How does the 'explicit' keyword affect C++ copy constructors and function parameters?

134 Views Asked by At

The "explicit" keyword to modify the copy constructor can cause problems. Objects passed as function parameters are particularly susceptible to these issues.

here are my codes:

#include <iostream>
#include <string>

class Pig{
public:
    std::string _name;
public:
    Pig(std::string n) : _name(n) {}
    //~Pig() = default;
    explicit Pig(const Pig &other) {
        std::cout << "copy ctor!" << std::endl;
        this->_name = other._name;
    }
};

void show(Pig p) {
    std::cout << p._name << std::endl;
}

int main() {
    Pig pig{std::string("hello")};
    show(Pig{pig});     // no pass
    // show(Pig(pig));  // no pass
    return 0;
}

compiler version: g++ (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0.

The code mentioned above does not compile with c++14 or lower versions, but compiles successfully with c++17 and later versions.

here are the compiler's errors:

test.cpp: In function ‘int main()’:
test.cpp:22:7: error: cannot convert ‘Pig’ to ‘std::string’ {aka ‘std::__cxx11::basic_string<char>’}
   22 |  show(Pig{pig});     // 不通过
      |       ^~~~~~~~
      |       |
      |       Pig
  • I am aware of how to use explicit and I would like to call the copy constructor explicitly in order to make it work.

Thanks in advance!

I tried compiling with c++14 and c++17.

1

There are 1 best solutions below

0
On

Prior C++17

The problem is that prior to C++17, there will be a conceptual copy of the argument Pig{pig} when passing it as an argument to the show function. That is, the parameter named p of the show function is copy initialized from the passed argument Pig{pig} and since the copy ctor is marked explicit this gives you the mentioned error.

This can be seen from copy initialization:

Copy initialization is performed in the following situations:

  • When passing an argument to a function by value.

(emphasis mine)


C++17

OTOH starting with C++17 the prvalue Pig{pig} is constructed directly into the storage for p. That is, there is no conceptual copy of the argument from C++17 and therefore no need for the copy ctor to be non-explicit etc.

From C++17 we have mandatory copy elision:

Under the following circumstances, the compilers are required to omit the copy and move construction of class objects, even if the copy/move constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. The copy/move constructors need not be present or accessible:

  • In the initialization of an object, when the initializer expression is a prvalue of the same class type (ignoring cv-qualification) as the variable type:

(emphasis mine)

Note that Pig{pig} is a prvalue and so the above applies and the copy constructor doesn't need to be present or accessible(and it can be explicit).