2

I am learning about explicit keyword in C++ using the resources listed here and as this post says:

Prefixing the explicit keyword to the constructor prevents the compiler from using that constructor for implicit conversions.


Now I wrote the following example that uses the explicit copy constructor B::B(const B&) in an implicit conversion. The example is as follows: MRE with C++14 and -fno-elide-constructors flag

struct A {
    A()
    {
    std::cout<<"A's default ctor called"<<std::endl;
    }
    A(const A&)
    {
        std::cout<<"A's copy ctor called"<<std::endl;
    }
};
class B
{
public:
    B()
    {
        std::cout<<"B default ctor called"<<std::endl;
    }
    B(A)
    {
        std::cout<<"B's parametrized ctor called"<<std::endl;
    }
    explicit B(const B&)
    {
        std::cout<<"explicit B copy ctor called"<<std::endl;
    }
};



int main()
{
    B x;
    //B a = x; //this fails AS EXPECTED

    A y;

    B p = y; //Why doesn't this fail as well? This uses the copy ctor in addition to the param ctor. How the explicit copy ctor is used here in implicit conversion

   
}

The output of the above program is:

B default ctor called
A's default ctor called
A's copy ctor called
B's parametrized ctor called
explicit B copy ctor called   <--------------HOW IS THAT EXPLICIT CTOR IS USED HERE IN IMPLICIT CONVERSION?

So my question is that how is it possible to use an explicit copy ctor in the above implicit conversion. I mean to my current understanding I expected B p = y; to fail just like the copy initialization B a = x; fails. I want to know is this allowed by the standard or is it a compiler bug.

Note

Note the use of -fno-elide-constructors flag in my given demo.

Jason
  • 36,170
  • 5
  • 26
  • 60
Kal
  • 475
  • 1
  • 16
  • 3
    `B(A)` is a so-called converting constructor. Why do you think `explicit B(const B&)` would need to be called? Are you confusing assignment with initialization? `B p = y;` is only initialization of `p` from `y` – 463035818_is_not_an_ai Jul 04 '22 at 12:52
  • @463035818_is_not_a_number I know `B p = y;` is Initialisation. Read carefully my question it is asking why the explicit copy constructor is called for `B p= y;` – Kal Jul 04 '22 at 12:54
  • @Kal Because here **direct initialization** will be used to initialize `p` from the prvalue and in direct initialization explicit ctors can be used. See my answer below. – Jason Jul 04 '22 at 12:56
  • 1
    oh i see. Cannot reproduce https://godbolt.org/z/xr5M6M4Tv – 463035818_is_not_an_ai Jul 04 '22 at 12:56
  • @463035818_is_not_a_number Note the use of the -fno-elide-constructors flag in OP's demo. – Jason Jul 04 '22 at 12:58
  • @AnoopRana the link doesnt work for me and same results with the flag https://godbolt.org/z/b8T6fsEKz – 463035818_is_not_an_ai Jul 04 '22 at 12:59
  • 3
    @463035818_is_not_a_number Looks like Firefox default tracking protection breaks wandbox. Anyway, newest GCC uses C++17 by default IIRC, changing it to C++14 makes the result as in OP's output: https://godbolt.org/z/e7a6hdz48 – Yksisarvinen Jul 04 '22 at 13:01
  • @463035818_is_not_a_number I think you missed the `C++14` tag in OP's question. With C++14 and -fno-elide-constructors flag it is reproducible on godbolt: [MRE with C++14 and -fno-elide-constructors flag](https://godbolt.org/z/vbd61WEvT) – Jason Jul 04 '22 at 13:02
  • 2
    Dup of [Should an explicit copy constructor be ignored?](https://stackoverflow.com/questions/32427433/should-an-explicit-copy-constructor-be-ignored) – Language Lawyer Jul 04 '22 at 13:10

1 Answers1

4

I want to know is this allowed by the standard or is it a compiler bug.

This is well-formed and is allowed by the standard as explained below.

The behavior of your program can be understood using dcl.init#17.6.2 which states:

Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in 13.3.1.4, and the best one is chosen through overload resolution (13.3). If the conversion cannot be done or is ambiguous, the initialization is ill-formed. The function selected is called with the initializer expression as its argument; if the function is a constructor, the call initializes a temporary of the cv-unqualified version of the destination type. The temporary is a prvalue. The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization.

(emphasis mine)

Let's apply this to your example.

In the given example(B p = y; in particular) the source type is A while the destination type is B. Now there is a user-defined conversion available from the source type to the destination type using the non-explicit parameterized ctor B::B(A) which will be used here.

This non-explicit ctor will be called with the initializer expression y as its argument. Moreover, this call is a prvalue of type B.

The important thing to note here is that this call will be used to direct-initialize the object named p. And since in direct initialization context the explicit ctor can be used, therefore the explicit copy ctor can and will be used here.

The effect is as-if we're writing:

B p{/*prvalue of type B obtained from call to parameterized ctor*/};
Jason
  • 36,170
  • 5
  • 26
  • 60