Is there any way to ensure members of a non-aggregate type are initialized with no copy and no move, i.e. using in-place construction? Suppose I have a heavy type
#include <iostream>
#include <string>
#include <utility>
struct S {
std::string x;
explicit S(std::string x) : x{x} { std::cout << "1-arg ctor\n"; }
S(const S &other) : x{other.x} { std::cout << "copy ctor\n"; }
S(S &&other) noexcept : x{std::move(other.x)} {
std::cout << "move ctor\n";
}
~S() noexcept { std::cout << "dtor\n"; }
};
Note: The std::string
is just for example purposes, I know string_view
could be used instead of taking the string by value.
Apparently, I get a different sequence of copies/moves if I try to construct an S
as a member of another struct depending on whether that stuct is an aggregate type:
struct Agg {
S s;
};
struct NotAgg {
S s;
explicit NotAgg(S s) : s{s} {};
};
struct NotAggMove {
S s;
explicit NotAggMove(S &&s) : s{std::move(s)} {};
};
int main() {
std::cout << "=== AGG ===\n";
{ Agg agg{S("hello world this is a long string")}; }
std::cout << "=== NOT AGG ===\n";
{ NotAgg not_agg{S("hello world this is a long string")}; }
std::cout << "=== NOT AGG MOVE ===\n";
{ NotAggMove not_agg_move{S("hello world this is a long string")}; }
std::cout << "=== DONE ===\n";
return 0;
}
The above code (live demo: https://godbolt.org/z/o5shr8nME) produces the following output in all of (GCC, Clang, MSVC) with optimizations turned on:
=== AGG ===
1-arg ctor
dtor
=== NOT AGG ===
1-arg ctor
copy ctor
dtor
dtor
=== NOT AGG MOVE ===
1-arg ctor
move ctor
dtor
dtor
=== DONE ===
As we can see, in the aggregate type, the S
is constructed directly into the aggregate with no copies and no moves, but the non-aggregates both have a spurious copy or move. In the case of the move, I guess I can understand it as analogous to a pessimizing move preventing RVO, like returning std::move(local_variable)
where normally the compiler would use RVO, but since I explicitly moved it gives me a move. However, I don't see any reason why the compiler can't elide the copy in the second case.
The argument to NotAgg(S s)
is a prvalue so the copy into the argument s
can be elided, then s
is used to initialize the member s
, which is quite similar in my mind to returning a local variable.
Actual question: Is it possible to design my non-aggregate class NotAgg
and/or call the constructor or otherwise create the struct in such a way that the compiler is allowed to construct the member s
in-place, i.e. with no copy and no move? Or is this only possible for aggregates?