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?