13

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?

Barry
  • 286,269
  • 29
  • 621
  • 977
chouquette
  • 971
  • 1
  • 7
  • 12
  • operator Foo* has higher priority than template operator, and Foo* is implicitly convertible into bool, so the compiler picks Foo* overload rather than the template one. – Creris May 06 '15 at 13:15
  • Shouldn't a potential instantiation creating an identity conversion be of higher priority? It seems that the constness is mostly what makes the correct conversion being selected – chouquette May 06 '15 at 13:17
  • apparently not if it is templated – Creris May 06 '15 at 13:18
  • @Creris That's incorrect. – Barry May 06 '15 at 13:40

2 Answers2

6

The relevant rules are defined in [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.

Let's just look at the first bool case. We have two viable candidates:

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

Both are called with a non-const Test. For the 2nd overload, no conversions are necessary - the conversion sequence is simply Exact Match. However, for the first overload, the implicit this argument needs to undergo a qualification conversion from Test to const Test. Thus, the second overload is preferred - we do not get to the second step which discusses return type.

If we dropped the const however, the viable candidates become:

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

Here, both candidates are equally viable with identical conversion sequences, but the bool template is preferred since the conversion sequence from the return type bool to bool (Identity - the highest rank) is a better conversion sequence than from Foo* to bool (Boolean Conversion - the lowest).

Barry
  • 286,269
  • 29
  • 621
  • 977
3

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.

ecatmur
  • 152,476
  • 27
  • 293
  • 366