Search code examples
c++templatesoverload-resolution

Template conversion operator priority & constness


I have something along the lines of:

#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;
}

It builds fine, but when I run it, while I would expect to get

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

I instead get

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

If I remove the const, or make the Test instance const, then everything works as expected. More precisely, the overload selection seems to make sense strictly when both operators have the same const qualification.

13.3.3.1.2 point of the standard makes me think I should get an identity conversion, converting to a bool, using the template conversion operator instantiation with a T = bool, though there is obviously a subtlety hiding somewhere. Could someone enlighten me as to what rule comes into play here?


Solution

  • When comparing conversion sequences, the conversions on the parameters are considered before the conversion of the result type. The implicit object parameter (the this pointer) is considered as a parameter, and a qualification conversion (Foo -> Foo const) is worse than the identity conversion on the implicit object parameter. From [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, [...]

    So a non-const-qualified member conversion operator will always be better than a const-qualified one, even if the result conversion is exact on the latter.