4

Here is a C++ 14 program for comparing direct initialisation (no =) with copy initialisation (=):*

#include <iostream>


struct A {
    A(int) { std::cout << "A(int)" << std::endl; }
    A(A&) { std::cout << "A(A&)" << std::endl; }
    A(A&&) { std::cout << "A(A&&)" << std::endl; }
};


int main() {
    A a(1);     // direct initialisation
    A b{1};     // direct initialisation
    A c = 1;    // copy initialisation
    A d = (1);  // copy initialisation
    A e = {1};  // copy initialisation
}

Compiling the program with copy elision disabled and running it:

$ clang++ -std=c++14 -fno-elide-constructors main.cpp && ./a.out

produces the following output:

A(int)
A(int)
A(int)
A(A&&)
A(int)
A(A&&)
A(int)

Why does copy initialisation with braces (A e = {1};) elide copy/move construction (even with copy elision disabled)?


* The motivation behind this comparison was to understand how a function return statement (return expression) works since C++ 11. When returning by value, one can use direct initialisation or copy initialisation of the function return value. The latter is more complex than copy initialisation of a variable like here, because initialising the function return value from the object denoted by expression involves trying to call the move constructor of the function return type first (even if expression is an lvalue) before falling back on its copy constructor. And since C++ 17, that copy/move construction is guaranteed to be elided if expression is a prvalue (mandatory return value optimisation), while it might be elided if expression is a glvalue (optional named return value optimisation).

Géry Ogam
  • 6,336
  • 4
  • 38
  • 67
  • Cases c and e never involve the copy constructor in the first place. – jamesdlin Apr 20 '20 at 19:48
  • @jamesdlin: `c` does in C++14, though it is almost certainly always to be elided. Copy-initialization of a `T` from an object that isn't a `T` requires the creation of a temporary of type `T` via conversion, then using that temporary to initialize `c`. C++17's guaranteed elision rules make the "temporary" into a prvalue of type `T` and thus skip any copy/move from the prvalue created to the object being initialized. – Nicol Bolas Apr 20 '20 at 19:57
  • 1
    I think it is correct to say that `T x = {y};` is identical to `T x{y};` other than checking for explicit constructors? – M.M Apr 21 '20 at 01:20
  • @NicolBolas "Copy-initialization of a `T` from an object that isn't a `T` requires the creation of a temporary of type `T` via conversion, then using that temporary to initialize `c`" I guess you meant via *implicit* conversion, and that `A a = A(1);` would be the creation of a temporary object via *explicit* conversion? – Géry Ogam Apr 21 '20 at 01:35
  • 2
    Dup of [Does copy list initialization invoke copy ctor conceptually?](https://stackoverflow.com/questions/26964221/does-copy-list-initialization-invoke-copy-ctor-conceptually) – Language Lawyer Apr 21 '20 at 02:13
  • @M.M Yes, cf. the link provided by LanguageLawyer. – Géry Ogam Apr 21 '20 at 10:21

1 Answers1

6

copy initialisation with braces

There is no such thing. If you use a braced-init-list to initialize an object, you are performing some form of list initialization. There are two forms of this: copy-list-initialization, and direct-list-initialization. In C++14, these have no relation to copy-initialization and direct-initialization (technically, direct-list-initialization is a grammatical form of direct-initialization, but since list-initialization bypasses everything that direct-initialization would have done, it's easier to say that direct-list-initialization is its own beast).

List initialization as a concept initializes an object. Using Typename t{} is direct-list-initialization, while Typename t = {} is copy-list-initialization. But regardless of which form is involved, no temporary is created; list initialization initializes the object in question. The only object in your example is e, so that is the object which gets initialized.

In accord with the C++14 rules for list-initialization, e gets initialized by calling a constructor, passing it a value of 1, which is the only value in the braced-init-list.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Is the fact that both direct-list-initialisation and copy-list-initialisation do not create temporary objects the reason why braces are called the [uniform initialisation syntax](https://softwareengineering.stackexchange.com/q/133688/184279)? – Géry Ogam Apr 20 '20 at 23:04
  • 1
    @Maggyero: No; it was deemed "uniform initialization" because the idea was that the initialization rules would be the same in all places, that regardless of how you used the `{}`, for any given `T` being initialized and for any given set of parameters, the same rules for initialization would apply. Obviously, the whole copy/direct-list-initialization distinction made that non-functional.. – Nicol Bolas Apr 20 '20 at 23:36