c++c++17c++14language-lawyerconversion-operator

Preference of conversion operator over copy constructor changes from C++14 to C++17?


I was playing with the following code.

#include <iostream>

struct To
{
    To() = default;
    To(const struct From&) {std::cout << "Constructor called\n";}
    To(const To&) {std::cout << "Copy constructor called\n";}
};

struct From
{
    operator To(){std::cout << "Conversion called\n"; return To();}
};

int main()
{
    From f;
    To t(f);
    To t1 = f;
    To t2{To(f)};
}

If I use -std=c++14 both GCC and Clang agree to the following output.

Constructor called
Conversion called
Constructor called

However, If I compile with -std=c++17, both compilers agree to

Conversion called
Conversion called
Conversion called

I understand that the reduction of several-expected output lines to output 3 lines is due to copy elision, but I can't figure out what changed in C++17 that resulted in this output. What change exactly in standard did initiate this?


Solution

  • Direct-initialization of an object of class type, in all versions of C++, performs overload resolution among the constructors of the class; see for example [dcl.init.general]/16.6.2 from the current draft. There are some exceptions, but they're not applicable here. It's important to note that conversion functions are not candidates for the initialization, so the compiler has to choose whether to use To::To(const To&) or To::To(const From&) for the initialization. The latter wins overload resolution because the parameter type has an exact match with the argument type.

    The interesting thing is that if To::To(const From&) were not present, then To::To(const To&) would be called, and the From object's conversion function would be called in order to initialize the const To& parameter. This is not great, because it forces a copy to occur; since From::operator To directly creates a To object, it would be better if From::operator To directly initialized the target of the initialization, rather than creating a temporary object that has to be copied to the target. Unfortunately, the current standard requires the copy to occur. This is the topic of CWG2327.

    The committee has not yet decided how to resolve CWG2327, but Clang and GCC have each implemented a different tweak to their overload resolution rules in order to allow copies to be elided under some circumstances. So basically, their behaviour in C++17 mode doesn't conform to the standard, but is "optimistic", i.e., the implementors hoped that the resolution to CWG2327 would be something resembling the tweaks they implemented.

    You can read more detail here. An updated version of this paper, which will propose a formal resolution to CWG2327, will be in the next mailing.