Mr. Wonko
Mr. Wonko

Reputation: 730

Efficient object construction supporting empty initializer lists

I have a class like this, except with multiple members:

struct S {
    S( S&& s ) : member( std::move( s.member ) ) {}
    std::vector< int > member;
};

I basically want aggregate initialization on it like so:

S s{ {} };

But that seems to be impossible due to the custom constructor, so I'd like to emulate it using constructor arguments:

struct S {
    S( S&& s ) : member( std::move( s.member ) ) {}
    S( std::vector< int >&& vec ) : member( std::move( vec ) ) {}
    S( const std::vector< int >& vec ) : member( vec ) {}
    std::vector< int > member;
};

That looks like a lot of redundancy just to move if possible and copy otherwise, especially with more members since the number of permutations explodes. I think I want perfect forwarding?

struct S {
    template< typename V >
    S( V&& vec ) : member( std::forward< V >( vec ) ) {}
    S( S&& s ) : member( std::move( s.member ) ) {}
    std::vector< int > member;
};

But now I can't call it with an empty initializer list like so:

S s( {} );

Because the initializer list argument type can't be deduced. Is there any solution that doesn't require me to write

S s( std::vector<int>{} );

?

Upvotes: 0

Views: 118

Answers (1)

Jonathan Wakely
Jonathan Wakely

Reputation: 171373

It's not clear why adding a default constructor isn't an option for you, allowing simply S s{}:

struct S {
    S() = default;
    S( S&& s ) = default;
    std::vector< int > member;
};

If that isn't suitable you have a number of options. The most obvious is to get rid of your move constructor, so the class is an aggregate. The move constructor you've shown is pointless anyway, it doesn't do anything the implicitly-defined one wouldn't do.

struct S {
    std::vector< int > member;
};

The next option is to add an initializer-list constructor:

struct S {
    S() = default;
    S( S&& s ) = default;
    S(std::initializer_list<int> il) : member(il) { }
    std::vector< int > member;
};

That will allow you to pass a braced-init-list to the vector member.

Another option, if you only want to be able to pass an empty initializer list to the vector, is to overload the constructor so that passing something to the vector is optional (but you can still pass arguments for the other members). That means you simply omit the pointless {} argument for the vector and let it be default-constructed.

Upvotes: 5

Related Questions