2

Probably my understanding of explicit is insufficient, but I wonder why in the following code the copy constructor is not hidden by the unversal reference constructor when I declare the latter as explicit.

struct A
{
    A() = default;

    template<typename T>
    A(T&& t) { std::cout<<"hides copy constructor"<<std::endl; }
};

struct A_explicit
{
    A_explicit() = default;

    template<typename T>
    explicit A_explicit(T&& t) {  std::cout<<"does not hide copy constructor?"<<std::endl; }
};

int main()
{
    A a;
    auto b = a; (void) b;  //prints "hides copy constructor"

    A_explicit a_exp;    
    auto b_exp = a_exp; (void) b_exp; //prints nothing
}

DEMO

Is that a general solution instead of the SFINAE stuff one would apply otherwise to prevent the hiding in A (for example by std::enable_if_t<!std::is_same<std::decay_t<T>, A>::value>, see here)?

davidhigh
  • 14,652
  • 2
  • 44
  • 75
  • For those interested, [here](http://en.cppreference.com/w/cpp/language/converting_constructor) is some explanation which I found based on the answers. – davidhigh May 08 '15 at 11:32

2 Answers2

6

In A, the copy constructor is not hidden. The compiler implicitly declares it, as it always does. It simply loses overload resolution because its parameter type (const A&) has extra cv-qualification compared to the parameter of the specialization of the constructor template (A&). If you would do

auto b = static_cast<const A&>(a);

you would see that the copy constructor would be called.

In A_explicit, the template is not submitted as a candidate to overload resolution at all, because it is declared explicit. The implicitly declared copy constructor is still there, like it is in A, so it is called.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • thanks, but I didn't get the answer. Could you please explain a bit more in detail what happens when copying `A_explicit`? For example wht you mean by *the template is not submitted as a candidate to overload resolution at all*. – davidhigh May 08 '15 at 07:39
  • 1
    @davidhigh The function produced by instantiating the template has no chance of being called at all because the copy-initialization requires a non-explicit constructor. Whereas for `A` it's treated on equal footing with the copy constructor and the best match is chosen. – Brian Bi May 08 '15 at 07:43
5

A constructor marked as explicit does not participate in overload resolution during copy-initialization (A a = b;, among other things).

It does participate in copy-list-initialization (A a = {b1};), and causes the program to be ill-formed if selected.

... except when the thing inside the braces is an A or a class derived therefrom, in which case a recent defect report changed the rules to say that in this particular situation copy-initialization is performed instead - and so explicit constructors are once again just ignored entirely.

Very teachable, I know.

Is that a general solution instead of the SFINAE stuff one would apply otherwise to prevent the hiding in A?

No. Because that constructor will still win overload resolution for direct-initialization:

A_explicit a, b(a); // will call the constructor taking a forwarding reference
T.C.
  • 133,968
  • 17
  • 288
  • 421