简体   繁体   中英

Copy constructor vs std::initializer_list constructor

Imagine that we have:

struct S {
  struct S {
  S() { printf("%s\n", __PRETTY_FUNCTION__); }
  S(const S&) { printf("%s\n", __PRETTY_FUNCTION__); }
  S(S&&) { printf("%s\n", __PRETTY_FUNCTION__); }
  ~S() { printf("%s\n", __PRETTY_FUNCTION__); }

  S(std::initializer_list<S>) { printf("%s\n", __PRETTY_FUNCTION__); }
};

Which constructors should be called when S s2{S{}};? Is it ok that gcc and clang have different behavior?

Example: https://godbolt.org/z/qQxyp5

gcc (trunk) output:

S::S()
S::S(std::initializer_list<S>)
S::~S()
S::~S()

clang (trunk) output:

S::S()
S::~S()

GCC is correct here; list initialization does not allow for copy elision in C++17.

If you had done S s2(S{}); , this would be required to only call S 's default constructor, due to [dcl.init]/17.6.1 :

If the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the class of the destination, the initializer expression is used to initialize the destination object. [ Example: T x = T(T(T())); calls the T default constructor to initialize x. — end example ]

However, that only applies to copy initialization and direct initialization.

Doing S s2{S{}}; is list-initialization, which is its own entirely separate form of initialization with its own rules. Since S is not an aggregate, [dcl.init.list]3.6 would take over, which says:

Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution ([over.match], [over.match.list]).

Calling a constructor means invoking a specific function with a specific set of parameters. And that means that the prvalue S{} must be used to initialize the parameter to the selected constructor. Which means you have to call the copy/move constructor.

Regular, non-guaranteed, elision aren't allowed either. [class.copy.elision]/1 gives the 3 circumstances under which elision is allowed: return localVariableName , throw localVariableName , and catch(TypeName) if the catch matches what was thrown. This case is obviously none of these, so it doesn't qualify for regular elision.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM