A class must have a valid copy or move constructor for any of this syntax to be legal:
C x = factory();
C y( factory() );
C z{ factory() };
In C++03 it was fairly common to rely on copy elision to prevent the compiler from touching the copy constructor. Every class has a valid copy constructor signature regardless of whether a definition exists.
In C++11 a non-copyable type should define C( C const & ) = delete;
, rendering any reference to the function invalid regardless of use (same for non-moveable). (C++11 §8.4.3/2). GCC, for one, will complain when trying to return such an object by value. Copy elision ceases to help.
Fortunately, we also have new syntax to express intent instead of relying on a loophole. The factory
function can return a braced-init-list to construct the result temporary in-place:
C factory() {
return { arg1, 2, "arg3" }; // calls C::C( whatever ), no copy
}
Edit: If there's any doubt, this return
statement is parsed as follows:
- 6.6.3/2: "A return statement with a braced-init-list initializes the object or reference to be returned from the function by copy-list-initialization (8.5.4) from the specified initializer list."
- 8.5.4/1: "list-initialization in a copy-initialization context is called copy-list-initialization." ¶3: "if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution (13.3, 13.3.1.7)."
Do not be misled by the name copy-list-initialization. 8.5:
13: The form of initialization (using parentheses or
=
) is generally insignificant, but does matter when the initializer or the entity being initialized has a class type; see below. If the entity being initialized does not have class type, the expression-list in a parenthesized initializer shall be a single expression.14: The initialization that occurs in the form
T x = a;
as well as in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and aggregate member initialization (8.5.1) is called copy-initialization.
Both copy-initialization and its alternative, direct-initialization, always defer to list-initialization when the initializer is a braced-init-list. There is no semantic effect in adding the =
, which is one reason list-initialization is informally called uniform initialization.
There are differences: direct-initialization may invoke an explicit constructor, unlike copy-initialization. Copy-initialization initializes a temporary and copies it to initialize the object, when converting.
The specification of copy-list-initialization for return { list }
statements merely specifies the exact equivalent syntax to be temp T = { list };
, where =
denotes copy-initialization. It does not immediately imply that a copy constructor is invoked.
-- End edit.
The function result can then be received into an rvalue reference to prevent copying the temporary to a local:
C && x = factory(); // applies to other initialization syntax
The question is, how to initialize a nonstatic member from a factory function returning non-copyable, non-moveable type? The reference trick doesn't work because a reference member doesn't extend the lifetime of a temporary.
Note, I'm not considering aggregate-initialization. This is about defining a constructor.
struct CounterExample { }; CounterExample ce; CounterExample ce1{ ce }; /* ill-formed */
. – Lorisloritastruct CounterExample { CounterExample(CounterExample const&) = delete; int a; operator int() { return 42; } }; CounterExample factory() { return {}; } CounterExample c{factory()}; /* fine! */
– LorisloritaT && make_T() { return std::move(T(constructor_argument)); }
. Thestd::move
and the&&
are both required to make it compile. However, I cannot compile when I try to do eithermember_variable(make_T())
ormember_variable(std::move(make_T()))
. Is this where GCC is incorrect? My code makes use of C++11 features that clang doesn't support, so I can't check that way. – Marvellamarvellous