模板转换运算符优先级和常量

Template conversion operator priority & constness

我有一些类似的东西:

#include <iostream>

class Foo;

struct Test
{
    template <typename T>
    operator T() const //  <----- This const is what puzzles me 
    {
        std::cout << "Template conversion" << std::endl;
        return T{};
    }

    operator Foo*()
    {
        std::cout << "Pointer conversion" << std::endl;
        return nullptr;
    }
};

int main()
{
    Test t;

    if (t)
    {
        std::cout << "ahoy" << std::endl;
    }
    bool b = (bool)t;
    Foo* f = (Foo*)t;
}

它构建得很好,但是当我 运行 它时,我希望得到

$> ./a.out
Template conversion
Template conversion
Pointer conversion

我反而得到

$> ./a.out
Pointer conversion
Pointer conversion
Pointer conversion

如果我删除常量,或将测试实例设为常量,那么一切都会按预期进行。 更准确地说,当两个运算符具有相同的 const 限定条件时,重载选择似乎才有意义。

13.3.3.1.2 标准的要点让我觉得我应该进行身份转换,转换为 bool,使用带有 T = bool 的模板转换运算符实例化,尽管如此显然某处隐藏着微妙之处。有人能告诉我这里有什么规则吗?

比较转换序列时,参数的转换先于结果类型的转换。隐式对象参数(this指针)被认为是参数,限定转换(Foo -> Foo const)比隐式对象参数上的身份转换差。来自 [over.match.best]:

1 - [...] a viable function F1 is defined to be a better function than another viable function F2 if for all arguments i, ICSi(F1) is not a worse conversion sequence than ICSi(F2), and then

— for some argument j, ICSj(F1) is a better conversion sequence than ICSj(F2), or, if not that, [...]

因此,非 const 限定的成员转换运算符将始终优于 const 限定的成员转换运算符,即使后者的结果转换是准确的。

相关规则定义在[over.match.best]:

Given these definitions, a viable function F1 is defined to be a better function than another viable function F2 if for all arguments i, ICSi(F1) is not a worse conversion sequence than ICSi(F2), and then
(1.3) — for some argument j, ICSj(F1) is a better conversion sequence than ICSj(F2), or, if not that,
(1.4) — the context is an initialization by user-defined conversion (see 8.5, 13.3.1.5, and 13.3.1.6) and the standard conversion sequence from the return type of F1 to the destination type (i.e., the type of the entity being initialized) is a better conversion sequence than the standard conversion sequence from the return type of F2 to the destination type.

我们只看第一个bool案例。我们有两个可行的候选人:

Test::operator T<bool>() const;
Test::operator Foo*();

两者都使用非 const Test 调用。对于第二次重载,不需要转换 - 转换序列只是精确匹配。但是,对于第一次重载,隐式 this 参数需要进行从 Testconst Test 的限定转换。因此,第二个重载是首选 - 我们没有进入讨论 return 类型的第二步。

但是,如果我们放弃 const,则可行的候选者将变为:

Test::operator T<bool>();
Test::operator Foo*();

在这里,两个候选者都同样可行,具有相同的转换序列,但是 bool 模板是首选,因为从 return 类型 boolbool 的转换序列(Identity - the highest rank) 是一个比从 Foo*bool (Boolean Conversion - the lowest) 更好的转换序列。