Different cast operator called by different compilers

前端 未结 4 1981
深忆病人
深忆病人 2021-02-02 05:15

Consider the following short C++ program:

#include 

class B {
public:
    operator bool() const {
        return false;
    }
};

class B2 : pub         


        
4条回答
  •  无人及你
    2021-02-02 05:23

    Short

    The conversion function operator int() is selected by clang over operator bool() const since b is not const qualified, whereas the conversion operator for bool is.

    The short reasoning is that the candidate functions for overload resolution (with implicit object parameter in place), when converting b to bool are

    operator bool (B2 const &);
    operator int (B2 &);
    

    where the second one is a better match since b is not const qualified.

    If both functions share the same qualification (either both const or not), operator bool is selected since it provides direct conversion.

    Conversion via cast-notation, analyzed step by step

    If we agree that the boolean ostream inserter (std::basic_ostream::operator<<(bool val) as per [ostream.inserters.arithmetic]) is called with the value that results from a conversion of b to bool we can dig into that conversion.

    1. The cast expression

    The cast of b to bool

    (bool)b
    

    evaluates to

    static_cast(b)
    

    as per C++11, 5.4/4 [expr.cast] since const_cast is not applicable (not adding or removing const here).

    This static conversion is allowed per C++11, 5.2.9/4 [expr.static.cast], if bool t(b); for an invented variable t is well formed. Such statements are called direct-initialization as per C++11, 8.5/15 [dcl.init].

    2. Direct initialization bool t(b);

    Clause 16 of the least mentioned standard paragraph states (emphasis mine):

    The semantics of initializers are as follows. The destination type is the type of the object or reference being initialized and the source type is the type of the initializer expression.

    [...]

    [...] if the source type is a (possibly cv-qualified) class type, conversion functions are considered.

    The applicable conversion functions are enumerated, and the best one is chosen through overload resolution.

    2.1 Which conversion functions are available?

    The available conversion functions are operator int () and operator bool() const since as C++11, 12.3/5 [class.conv] tells us:

    A conversion function in a derived class does not hide a conversion function in a base class unless the two functions convert to the same type.

    While C++11, 13.3.1.5/1 [over.match.conv] states:

    The conversion functions of S and its base classes are considered.

    where S is the class that will be converted from.

    2.2 Which conversion functions are applicable?

    C++11, 13.3.1.5/1 [over.match.conv] (emphasis mine):

    1 [...] Assuming that “cv1 T” is the type of the object being initialized, and “cv S” is the type of the initializer expression, with S a class type, the candidate functions are selected as follows: The conversion functions of S and its base classes are considered. Those non-explicit conversion functions that are not hidden within S and yield type T or a type that can be converted to type T via a standard conversion sequence are candidate functions.

    Therefore operator bool () const is applicable since it is not hidden within B2 and yields a bool.

    The part with emphasis in the last standard quote is relevant for the conversion using operator int () since int is a type that can be converted to bool via standard conversion sequence. The conversion from int to bool is not even a sequence but a plain direct conversion which is allowed per C++11, 4.12/1 [conv.bool]

    A prvalue of arithmetic, unscoped enumeration, pointer, or pointer to member type can be converted to a prvalue of type bool. A zero value, null pointer value, or null member pointer value is converted to false; any other value is converted to true.

    This means that operator int () is applicable as well.

    2.3 Which conversion function is selected?

    The selection of the appropriate conversion function is performed via overload resolution (C++11, 13.3.1.5/1 [over.match.conv]):

    Overload resolution is used to select the conversion function to be invoked.

    There is one special "quirk" when it comes to overload resolution for class member functions: the implicit object parameter".

    Per C++11, 13.3.1 [over.match.funcs],

    [...]both static and non-static member functions have an implicit object parameter[...]

    where the type of this parameter for non-static member functions -according to clause 4- is:

    • “lvalue reference to cv X” for functions declared without a ref-qualifier or with the & ref-qualifier

    • “rvalue reference to cv X” for functions declared with the && ref-qualifier

    where X is the class of which the function is a member and cv is the cv-qualification on the member function declaration.

    This means that (per C++11, 13.3.1.5/2 [over.match.conv]), in an initialization by conversion function,

    [t]he argument list has one argument, which is the initializer expression. [ Note: This argument will be compared against the implicit object parameter of the conversion functions. —end note ]

    The candidate functions for overload resolution are:

    operator bool (B2 const &);
    operator int (B2 &);
    

    Obviously, operator int () is a better match if a conversion is requested using a non-constant object of type B2 since operator bool () required a qualification conversion.

    If both conversion functions share the same const qualification, overload resolution of those function won't do the trick anymore. In this case, conversion (sequence) ranking comes into place.

    3. Why is operator bool () selected when both conversion function share the same const qualification?

    The conversion from B2 to bool is an user-defined conversion sequence (C++11, 13.3.3.1.2/1 [over.ics.user])

    A user-defined conversion sequence consists of an initial standard conversion sequence followed by a userdefined conversion followed by a second standard conversion sequence.

    [...] If the user-defined conversion is specified by a conversion function, the initial standard conversion sequence converts the source type to the implicit object parameter of the conversion function.

    C++11, 13.3.3.2/3 [over.ics.rank]

    [...] defines a partial ordering of implicit conversion sequences based on the relationships better conversion sequence and better conversion.

    [...] User-defined conversion sequence U1 is a better conversion sequence than another user-defined conversion sequence U2 if they contain the same user-defined conversion function or constructor or aggregate initialization and the second standard conversion sequence of U1 is better than the second standard conversion sequence of U2.

    The second standard conversion is case of operator bool() is bool to bool (identity conversion) whereas the second standard conversion in case of operator int () is int to bool which is a boolean conversion.

    Therefore, the conversion sequence, using operator bool (), is better if both conversion function share the same const qualification.

提交回复
热议问题