Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 573
chouquette Avatar asked May 06 '15 12:05

chouquette


2 Answers

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).

like image 67
Barry Avatar answered Nov 09 '22 12:11

Barry


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.

like image 24
ecatmur Avatar answered Nov 09 '22 12:11

ecatmur