Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implicit conversion operator vs template constructor - who should be prioritized?

Tags:

Consider the following code snippet:

template <typename>
struct dependent_false { static constexpr auto value = false; };

struct foo
{
    foo() { }

    template <typename T>
    foo(const T&) { static_assert(dependent_false<T>::value, ""); }
};

struct proxy
{
    operator foo() { return foo{};  }
};

int main()
{
    (void) foo{proxy{}};
}

When compiling with -std=c++17:

  • clang++ (trunk) successfully compiles the code;

  • g++ (trunk) fails to compile the code - it instantiates foo(const T&).

When compiling with -std=c++11, both compilers reject the code. The new prvalue materialization rules in C++17 might affect the behavior here.

live example on godbolt.org


What's the correct behavior here?

  • Does the Standard guarantee that foo::foo(const T&) will be (or not be) instantiated?

  • Does the Standard guarantee that the implicit conversion operator will be preferred to the invocation of foo::foo(const T&), regardless of whether or not it is instantiated?

like image 823
Vittorio Romeo Avatar asked Jun 12 '18 16:06

Vittorio Romeo


1 Answers

This is CWG 2327:

Consider an example like:

struct Cat {};
struct Dog { operator Cat(); };

Dog d;
Cat c(d);

This goes to 11.6 [dcl.init] bullet 17.6.2:

Otherwise, if the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. The applicable constructors are enumerated (16.3.1.3 [over.match.ctor]), and the best one is chosen through overload resolution (16.3 [over.match]). The constructor so selected is called to initialize the object, with the initializer expression or expression-list as its argument(s). If no constructor applies, or the overload resolution is ambiguous, the initialization is ill-formed.

Overload resolution selects the move constructor of Cat. Initializing the Cat&& parameter of the constructor results in a temporary, per 11.6.3 [dcl.init.ref] bullet 5.2.1.2. This precludes the possitiblity of copy elision for this case.

This seems to be an oversight in the wording change for guaranteed copy elision. We should presumably be simultaneously considering both constructors and conversion functions in this case, as we would for copy-initialization, but we'll need to make sure that doesn't introduce any novel problems or ambiguities.

I believe clang implements this implied change (and so considers the conversion function a better match) and gcc does not (and so never actually considers the conversion function).

Per the standard, gcc is correct.

like image 183
Barry Avatar answered Sep 28 '22 07:09

Barry