Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pass initializer list or container, looking towards move semantics?

EDIT: Before we begin, this question is not about proper usage of std::initializer_list; it is about what should be passed when the convenient syntax is desired. Thank you for staying on topic.


C++11 introduces std::initializer_list to define functions accepting braced-init-list arguments.

struct bar {
    bar( char const * );
    bar( int );
} dog( 42 );

fn foo( std::initializer_list< bar > args );

foo( { "blah", 3, dog } );

The syntax is nice, but under the hood it is distasteful due to various problems:

  • They cannot be meaningfully moved. The above function must copy dog from the list; this cannot be converted to move-construction or elided. Move-only types cannot be used at all. (Well, const_cast would actually be a valid workaround. If there's an article about doing so, I'd like to see it.)

  • There are no constexpr semantics, either. (This is forthcoming in C++1y. It's a minor issue, though.)

  • const does not propagate as it does everywhere else; the initializer_list is never const but its contents always are. (Because it doesn't own its contents, it cannot give write access to a copy, although copying it anywhere would seldom be safe.)

  • The initializer_list object does not own its storage (yikes); its relationship to the completely separate naked array (yikes) providing the storage is hazily defined (yikes) as the relationship of a reference to a bound temporary (quadruple yikes).

I have faith these things will be fixed in due time, but for now is there a best practice to get the advantages without hard-coding to initializer_list? Is there any literature about or analysis into working around direct dependency on it?

The obvious solution is to pass by value a standard container such as std::vector. Once the objects are copied into it from the initializer_list, it is move-constructed to pass by value, and then you can move the contents out. An improvement would be to offer storage on the stack. A good library might be able to offer most of the advantages of initializer_list, array, and vector, without even using the former.

Any resources?

like image 907
Potatoswatter Avatar asked Jul 18 '13 05:07

Potatoswatter


Video Answer


1 Answers

it is about what should be passed when the convenient syntax is desired.

If you want the convenience of size (ie: the user just types a {} list with no function calls or words), then you must accept all the powers and limitations of a proper initializer_list. Even if you try to convert it into something else, like some form of array_ref, you still have to have an intermediary initializer_list between them. Which means that you can't get around any of the issues you've run into, like not being able to move out of them.

If it goes through an initializer_list, then you have to accept these limitations. Therefore, the alternative is to not go through an initializer_list, which means that you're going to have to accept some form of container with specific semantics. And the alternative type would have to be an aggregate, so that the construction of the alternate object won't encounter the same problem.

So you're probably looking at forcing the user to create a std::array (or a language array) and passing that. Your function could take some form of array_ref class, which can be constructed from any array of arbitrary size, so the consuming function isn't limited to one size.

However, you lose the convenience of size:

foo( { "blah", 3, dog } );

vs.

foo( std::array<bar, 3>{ "blah", 3, dog } );

The only way to avoid the verbosity here is to have foo take std::array as a parameter. Which means that it could only take an array of a specific fixed size. And you couldn't use C++14's proposed dynarray, because that will use an initializer_list intermediary.

Ultimately, you shouldn't be using uniform initialization syntax for passing around lists of values. It's for initializing objects, not for passing lists of things. std::initializer_list is a class who's sole purpose is to be used to initialize a specific object from an arbitrarily long list of values of identical types. It is there to serve as an intermediary object between a language construct (a braced-init-list) and the constructor that these values are to be fed into. It allows the compiler to know to call a particular constructor (the initializer_list constructor) when given a matching braced-init-list of values.

This is the entire reason why the class exists.

Therefore, you should use the class exclusively for the purpose for which it was devised. The class exists to tag a constructor as taking a list of values from a braced-init-list. So you should use it only for constructors that take such a value.

If you have some function foo that acts as an intermediary between some internal type (that you don't want to directly expose) and a user-provided list of values, then you need to take something else as a parameter to foo. Something that has the semantics you desire, which you can then feed into your internal type.

Also, you seem to have a misconception around initializer_lists and movement. You cannot move out of an initializer_list, but you can certainly move into one:

foo( { "blah", 3, std::move(dog) } );

The third entry in the internal dog array will be move-constructed.

like image 54
Nicol Bolas Avatar answered Nov 15 '22 17:11

Nicol Bolas