> Probably it is to late to make such a change, and the initializer_list willIt clearly is too late and I recommend to work on updated proposals
> not take move into account.
similar to the direction of n2801 mentioned above.
The initializer_list constructor overloads were added to containers
and other types as an additional convenience, not as a replacement for
the iterator-based constructors or as a replacement for usage of
emplace. So to me it seems now that you are inverting the roles of
std::initializer_list constructors to the most fundamental ones.
I also cannot really share the interpretation, that RAII is not
possible for move-only types, but given any of the alternatives
mentioned above it seems still valid to me.
I also cannot really share the interpretation, that RAII is not
possible for move-only types, but given any of the alternatives
mentioned above it seems still valid to me.The alternatives above aren't applicable, since to use an iteratorconstructor, I'd need to have something iterable. And emplace isn't
initialization.
The alternatives above aren't applicable, since to use an iteratorconstructor, I'd need to have something iterable. And emplace isn't
initialization.
It's not tooooo hard to make it into something iterable, that can be moved from:
MoveOnly arr[] = { foo(), bar(), baz() };
std::vector<MoveOnly> v(std::make_move_iterator(std::begin(arr)), std::make_move_iterator(std::end(arr)));
If this is not good enough, it should be possible to write a function
template of the form
template<class T, std::size_t N>
std::move_iterator<T> move_away(T (&& t)[N]);
and wait for the expect range-support to allow something like:
std::vector<MoveOnly> v(move_away({MoveOnly(1), MoveOnly(2)}));
[untested]
2) Alternatively you can create a local container, emplace the
elements, then move this container into the actual target container
(typically all this is provided by a single function template).
> I think it would be worthwhile to explore whether we can do a fix that> doesn't require such work-arounds. The "wait for the expected range-support" rings an alarmI don't understand why. Are you against range-support? IMO
> bell in my head.
std::initializer_list constructors in the library are just a special
case of a more generalized range support pattern and I find it more
reasonable to look for a more general solution instead of trying to
mutate std::initializer_list to something that it was not intended to
be.
That's ok, I don't know what the solution should be yet, perhaps
mutating initializer_list isn't it. The problem shouldn't be "this is how
initializer_lists work, don't expect braced-initializers to work differently".
I'm not supposed to KNOW there's an initializer_list involved whenI construct a vector from a braced list. At least I'm under the impression
that initializer_list was supposed to be implementation plumbing
rather than something that's in-my-face. And again, I don't think
that implementation plumbing works quite right if I can't braced-init
with move-only types. Other people can freely agree to disagree
with that.
That's ok, I don't know what the solution should be yet, perhaps
mutating initializer_list isn't it. The problem shouldn't be "this is how
initializer_lists work, don't expect braced-initializers to work differently".
I'm not supposed to KNOW there's an initializer_list involved whenI construct a vector from a braced list. At least I'm under the impression
that initializer_list was supposed to be implementation plumbing
rather than something that's in-my-face. And again, I don't think
that implementation plumbing works quite right if I can't braced-init
with move-only types. Other people can freely agree to disagree
with that.
The std::initializer_list<int> is only the solution of the uniform initialization problem,
not the problem itself. So while the design of initializer list is perfectly valid (thanks
Daniel for the papers), this model failed to achieve the main goal:
Provide uniform initialization for build-in types (arrays) and
user defined types.
That is the slogan/promotional phase for the most of materials about C++11.
But the initialization of the vector is not uniform with the build-in arrays, which
is suprising for the users and make learning of C++ harder, not easer as
uniform initialization suppose to. Especially when no presentation/material know to me
on C++11 mentions this - they use the slogan.
Was another approach possible?
I think yes. To point is to not use 2-face initialization and use varidatic templates instead:
Of course this code needs allocator support (std::allocator_arg should be used), but this
is doable.
This will of course make writing an STL-like container even harder, but the allocator support
and exception safety already make this a really hrd task so that should not be a problem. Also
there will be some problem with ambiguity of constructor. But from the user perspective the goal will
be achived - the vector intialization will be uniform with intialization of build in arrays, even considering
performance.
There are a lot more uses for having an object with an initializer_list constructor than just an STL-compliant container type. We shouldn't make you have to go through that much effort to process an array.
As people have said, this is a solveable problem. We may need a new "initializer_list" type to solve it, but it can certainly be resolved under the existing paradigm.
The core of the problem seems to be that initializer_list<T> does not own its elements, not that the elements are immutable. If we merely made the contents of the initializer_list mutable, we would break move-safety:initializer_list<unique_ptr<T>> x = { a, b, c };auto get() { return x; }std::vector<unique_ptr<T>> v(get()); // oops, implicitly move here
It makes the compiler errors you get for creating a braced-init-list of different types much more oddball.
It causes the generation of lots of pointless code, bloating compile times needlessly (it's a list of known size. We shouldn't have to template metaprogram recurse through it).
It also conflicts with other parts of uniform initialization: being able to call non-initializer_list constructors with braced-init-lists. Lastly, there's no way to differentiate between a variadic template intended for some other purpose and an object that you want to be constructed from an arbitrary list of values
Of course this code needs allocator support (std::allocator_arg should be used), but this
is doable.
This will of course make writing an STL-like container even harder, but the allocator support
and exception safety already make this a really hrd task so that should not be a problem. Also
there will be some problem with ambiguity of constructor. But from the user perspective the goal will
be achived - the vector intialization will be uniform with intialization of build in arrays, even considering
performance.
There are a lot more uses for having an object with an initializer_list constructor than just an STL-compliant container type. We shouldn't make you have to go through that much effort to process an array.
As people have said, this is a solvable problem. We may need a new "initializer_list" type to solve it, but it can certainly be resolved under the existing paradigm.
On Thursday, August 15, 2013 11:29:53 AM UTC+8, Richard Smith wrote:The core of the problem seems to be that initializer_list<T> does not own its elements, not that the elements are immutable. If we merely made the contents of the initializer_list mutable, we would break move-safety:initializer_list<unique_ptr<T>> x = { a, b, c };auto get() { return x; }std::vector<unique_ptr<T>> v(get()); // oops, implicitly move here
Presuming this is supposed to go inside a class, this example is nonsense.
Whatever functionality was agreed upon previously, its interface currently overextends its capabilities. We should fix it or deprecate it for future semantic redefinition.
As it is, initializer_list supports the usual syntax of value-semantic objects without supporting the semantics. As the language gets richer, it only becomes easier to fall into a trap. Is this valid C++14?
auto f() { return { 1, 2, 3 }; } // dangling reference!
On Wed, Aug 14, 2013 at 9:19 AM, Daniel Krügler
<daniel....@gmail.com> wrote:
> MoveOnly data[] = { .... };
> std::vector<MoveOnly> v(std::make_move_iterator(std::begin(data)),
> std::make_move_iterator(std::end(data)));
What I don't understand is, why we need initializer_list? Variadic
template can handle any case:
No, it's not valid, see http://open-std.org/JTC1/SC22/WG21/docs/papers/2013/n3638.html
It's ill-formed because it would indeed create a dangling reference.
std::intializer_list<T> and std::owning_initializer_list<T>:
I think i would be enough to define constructor of std::initializer_list<T> as
const reference to std::owning_initializer_list<T> cons& with the same semantics
as initializer_list<T> has for array now.
The point of non-movabilty and the ownership semantics is to disallow the use of owning_initlizer_list (by value) in return type or as the class member, which results in dangling reference, the dynarray<T> should be used in such situations.
The idea is to allow the object of owning_initializer_list to be construed only by compiler (not user). That enforces storing it in class or returning via reference and this fall backs to already know cases: do not return reference to local object, do not store reference to temporary in class. Just remove one additional exception from language.
struct nm { nm( int v ) {} nm( nm const & ) = delete; // non-copyable nm( nm && ) = delete; // non-movable }; struct qc { nm m{ 5 }; // direct-initialization of class member } o; nm qf() { return { 5 }; // direct-initialization of object in calling scope } void f() { nm && qo = qf(); // initialize a persistent local }
On Wednesday, August 21, 2013 3:06:45 PM UTC+8, toma...@gmail.com wrote:The point of non-movabilty and the ownership semantics is to disallow the use of owning_initlizer_list (by value) in return type or as the class member, which results in dangling reference, the dynarray<T> should be used in such situations.
The idea is to allow the object of owning_initializer_list to be construed only by compiler (not user). That enforces storing it in class or returning via reference and this fall backs to already know cases: do not return reference to local object, do not store reference to temporary in class. Just remove one additional exception from language.
A noble goal, but it is not accomplished by non-movability. As I mentioned, you can use a non-movable return value by binding it to a reference. And non-movability does nothing to hinder class membership.
The std::initializer_list<T> is defined as a non-owning handle to a const array of the T elements placed probably on stack, with requires from
containers constructor to copy the elements into container. This lead for usability problem, especially in generic programming.
The most common example of usage of std::initializer_list is to write:
std::vector<int> v{1,2,3,4};
instead of:
std::vector<int> v; v.reserve(4);
v.emplace_back(1);
v.emplace_back(2);
v.emplace_back(3);
v.emplace_back(4);
There is not difference between this two codes, because the copy and move constructor of int does not differ.
template<typename T>
class make_vector
{
std::vector<T> data;
public:
template<typename... A>
inline make_vector(A&&... a)
{
data.reserve(sizeof...(A));
auto f = {(data.emplace_back(std::forward<A>(a)), 0)...}; //hack to reduce code size
}
operator std::vector<T>() { return std::move(data); }
};
int main()
{
std::vector<std::unique_ptr<int>> v = make_vector<std::unique_ptr<int>>
{
std::unique_ptr<int>(new int{1}),
std::unique_ptr<int>(new int{2}),
std::unique_ptr<int>(new int{4}),
};
for(auto& i : v)
std::cout<< *i << "\n";
}