4

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.

Jason
  • 36,170
  • 5
  • 26
  • 60
Andrés
  • 41
  • 2
  • 1
    Here is a repro: https://godbolt.org/z/7Gvr49GPn – Marek R May 24 '23 at 14:20
  • 1
    @Dharmesh946 well, I ***can*** reporoduce this: https://godbolt.org/z/vnTjaz7bE ; This has to do with copy elision. Imho you should use `void show(Pig const& p)` as signature for your function anyways... – fabian May 24 '23 at 14:20
  • @fabian indeed, the code fails with C++14. It can be fixed by passing ```Pig``` as rvalue reference into show. I don't know what are the differences in rules with passing rvalue by value... – Oersted May 24 '23 at 14:24
  • 2
    @Dharmesh946 The difference is that in one case you'll pass a reference to the temporary object created by the expression `Pig{pig}` and in the other case you'll need to copy-construct the function parameter value from that temporary; for >=C++17 this copy is removed because of copy elision. – fabian May 24 '23 at 14:31
  • @fabian In order to understand: copy elision was possible only for returned values before C++17. Am I understanding correctly? If I interpret correctly, [copy elision](https://en.cppreference.com/w/cpp/language/copy_elision) says indeed that copy elision for input arg is possible since C++17. – Oersted May 24 '23 at 14:34
  • 1
    *How does the 'explicit' keyword affect C++ copy constructors and function parameters?* The compiler can no longer use the *explicit* copy constructor for *implicit* copies, which will cause tons of problems and no benefits. – Eljay May 24 '23 at 14:41
  • @Dharmesh946 There is "mandatory" copy elision in some situations(including OP's case) from c++17. – Jason May 24 '23 at 14:47
  • AFAIK, a *copy* constructor should not be `explicit`, only a *converting* constructor that has 1 parameter should be `explicit` if other 1-parameter constructors are present – Remy Lebeau May 24 '23 at 16:43

1 Answers1

5

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).

Jason
  • 36,170
  • 5
  • 26
  • 60