-2

I'm looking for a way to initialize non-moveable members in a structure through a factory function return value. Specifically, the language admits the case of initializing a non-moveable member through aggregate initialization, but there are scenarios in which this kind of initialization is not possible. I wonder if there is still a way to initialize non-moveable members directly (in-place) in cases where aggregate initialization is not permitted.

This is a similar question, although it deals with the case of initializing a non-static member from a function returning a non-copyable/moveable type.

An example is in order. Consider the following:

struct S { S(S&&) = delete; };       // Non-moveable type.
S F() { return {}; }

Now, consider the following initialization of a member in an aggregate from a return value.

struct T { S s; };
void G() { T t{F()}; }               // OK (in-place construction of t).

The above works because t is being initialized through aggregate initialization. S::S(S&&) is not involved. [Note: this excerpt works only in GCC-7, not in GCC-6 or older versions.]

But what if aggregate initialization is not possible at all?

struct X {};
struct U: virtual X                  // Not an aggregate anymore.
    { S s; U(S&& s): s{(S&&)s} {} }; // Needs an explicit constructor.
void H() { U u{F()}; }               // Doesn't work (S::S(S&&) is deleted).

...or simply inconvenient?

struct A {}; struct B {}; struct C {};
struct V: A, B, C { S s; };          // Still an aggregate.

void I() { V v{F()}; }               // No chance.
void J() { V v{{}, {}, {}, F()}; }   // Kinda works, but ugly.

The point here is that, although S in non-moveable, it would be nice if it could be used as a member object subject to in-place construction in situations similar to the above.

Is it possible?

alecov
  • 4,882
  • 2
  • 29
  • 55

2 Answers2

4

When you make a class immobile, you've made it immobile. Which means that you have to construct it in place, either from a prvalue, direct use of a constructor call, or direct use of a braced-init-list.

You cannot pass an object of that type through to someone else. If you want U to be able to construct an S member, you need to have U's constructor either directly or indirectly provide the parameters used to initialize the S object in-situ. For example:

struct U        // Constructors mean that it's not an aggregate.
{
  S s;
  template<typename ...Args>
  U(Args &&...args)
   : s(std::forward<Args>(args)...) {}
};

void H() { U u{}; } //Passes no parameters to `S`'s construction.

If you know exactly what constructors of the member to use in a constructor, then you don't need to use forwarding like this.


The above works because t is being initialized through aggregate initialization. S::S(S&&) is not involved. [Note: this excerpt works only in GCC-7, not in GCC-6 or older versions.]

Correction: the above works in GCC-7 because that supports guaranteed elision, which makes that possible. Without guaranteed elision, a prvalue would represent a temporary that gets copied/moved from. Even if the compiler elides it, the compiler still has to make sure that the code would have worked if it had copied/moved it.

I'm looking for a way to initialize non-moveable members in a structure through a factory function return value.

That's a rather different question, but guaranteed elision makes it work well enough. Simply pass a function to the constructor:

struct U
{
  S s;
  template<typename Func>
  U(Func f) : s(f()) {}
};

If your factory function takes parameters, then you need to pass a lambda that captures those parameters and passes the captured values to the factory function.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Thanks for the feedback. I'm actually using this method to solve the problem, although it doesn't scale well if there are other similar (ie. non-moveable) members involved. – alecov Jan 26 '18 at 02:30
  • @alecov: Objects which are fundamentally immobile should not be so common that scalability should be a problem. Move-support was created to serve the cases where copying is not appropriate, and the standard doesn't have `scoped_ptr` and such, because `const unique_ptr` works just fine in that regard. Even [`scope_exit` and its ilk](http://wg21.link/P0052) are move-constructible. The number of immobile types in the standard is few, and that is as it should be. – Nicol Bolas Jan 26 '18 at 16:23
  • Structures from C libraries are occasionally non-moveable; this is what has motivated this investigation. Indeed, `S s{F()}` works and `S s{(S&&)F()}` doesn't -- I think this is mostly a syntax limitation, in that the standard allows (or mandates) RVO in the first case, but not in the second. – alecov Jan 29 '18 at 17:17
  • @alecov: "*Structures from C libraries are occasionally non-moveable*" C doesn't have the concept of "moveable", so the question is essentially irrelevant for such types. They are either copyable or they are opaque. And if they're opaque, then you can only interact with them by pointer. – Nicol Bolas Jan 29 '18 at 17:34
  • _"They are either copyable or they are opaque."_ This is false -- there are non-opaque structures which are copyable merely from a language perspective, but copying such a structure is not always well-defined. `z_stream_s` from zlib is such a structure -- non-opaque and non-copyable. A C++ type which wraps such a structure cannot be copyable or moveable unless it handles the latter through pointers. – alecov Jan 29 '18 at 17:59
  • @alecov: I've never used zlib directly, but I don't see anything in the API that says copying the structure is illegal. You simply can't call `inflate/deflateEnd` on both copies. Much like you cannot call `free` on copies of the same `malloc`ed pointer. – Nicol Bolas Jan 29 '18 at 18:18
0

When you return a S instance local variable, it is turned into an r-xvalue which would look first for the move constructor. If it is deleted, then it will not work.

Also from the standard: N4659 15.8.1/(10.4) Copy/move constructors [class.copy.ctor]

A deleted move constructor would otherwise interfere with initialization from an rvalue which can use the copy constructor instead

AdvSphere
  • 986
  • 7
  • 15