Moving elements out of an initializer-list

  • Thread starter Johannes Schaub (litb)
  • Start date

J

Johannes Schaub (litb)

It seems that the draft C++0x allows a container to act in the following
way:

template<typename T>
struct container {
/* t's backing array is a T[]. Wrap into move iterators to
* make use of move-construction. */
container(initializer_list<T> t)
:v(make_move_iterator((T*)t.begin()),
make_move_iterator((T*)t.end()))
{

}

std::vector<T> v;
};

Notice that i casted the return tpe of t.begin() and t.end() from "T const*"
to "T*", but that seems to be valid. Any comment?
 
Ad

Advertisements

V

Victor Bazarov

It seems that the draft C++0x allows a container to act in the following
way:

template<typename T>
struct container {
/* t's backing array is a T[]. Wrap into move iterators to
* make use of move-construction. */
container(initializer_list<T> t)
:v(make_move_iterator((T*)t.begin()),
make_move_iterator((T*)t.end()))
{

}

std::vector<T> v;
};

Notice that i casted the return tpe of t.begin() and t.end() from "T const*"
to "T*", but that seems to be valid. Any comment?

Could you elaborate on why do you need to cast? Thanks!

V
 
J

Johannes Schaub (litb)

Victor said:
It seems that the draft C++0x allows a container to act in the following
way:

template<typename T>
struct container {
/* t's backing array is a T[]. Wrap into move iterators to
* make use of move-construction. */
container(initializer_list<T> t)
:v(make_move_iterator((T*)t.begin()),
make_move_iterator((T*)t.end()))
{

}

std::vector<T> v;
};

Notice that i casted the return tpe of t.begin() and t.end() from "T
const*" to "T*", but that seems to be valid. Any comment?

Could you elaborate on why do you need to cast? Thanks!

The above class can be used to do the following

container<T> c{ ifstream("file1.txt"), ifstream("file2.txt") };

And then you have "c.v" a vector of size 2 with those ifstream elements. A
move_iterator returns "T&&" from its "operator*" such that when it iterates
over a "ifstream"-sequence it returns "ifstream&&" which can be moved from.
If I wouldn't have casted, it would return "ifstream const&&", which cannot
be moved from (it's const!).

But the following does *not* work

vector<ifstream> v{ ifstream("file1.txt"), ifstream("file2.txt") };

Because the const is not casted away, this forces vector to try and copy the
elements, but streams are non-copyable. I think this either shows

- A problem with the wording of initializer_list creation (the backing-up
array is non-const but is inteded to be const?)
- Missing wording that you are not allowed to modify the backing array of an
initializer_list (effectively forbidding moving things out of it)
- Something we can apply to Standard containers to make them more widely
usable with initializer lists.
- A poorly designed initializer_list interface: Why are you allowed to move
things out, but you would need an explicit cast? Either disallow it or
explicitly allow it and make begin/end return "T*".

This is my personal opinion, of course. Please let me hear what you think
about it.
 
V

Victor Bazarov

Victor said:
It seems that the draft C++0x allows a container to act in the following
way:

template<typename T>
struct container {
/* t's backing array is a T[]. Wrap into move iterators to
* make use of move-construction. */
container(initializer_list<T> t)
:v(make_move_iterator((T*)t.begin()),
make_move_iterator((T*)t.end()))
{

}

std::vector<T> v;
};

Notice that i casted the return tpe of t.begin() and t.end() from "T
const*" to "T*", but that seems to be valid. Any comment?

Could you elaborate on why do you need to cast? Thanks!

The above class can be used to do the following

container<T> c{ ifstream("file1.txt"), ifstream("file2.txt") };

And then you have "c.v" a vector of size 2 with those ifstream elements. A
move_iterator returns "T&&" from its "operator*" such that when it iterates
over a "ifstream"-sequence it returns "ifstream&&" which can be moved from.
If I wouldn't have casted, it would return "ifstream const&&", which cannot
be moved from (it's const!).

OK, hold on. The wording of [dcl.init.list/4] is:
"An object of type std::initializer_list<E> is constructed from an
initializer list as if the implementation
allocated an array of N elements of type E, where N is the number of
elements in the initializer list.
Each element of that array is copy-initialized with the corresponding
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
element of the initializer list, .. "

Now, how do you suppose the elements are copy-initialized if the streams
cannot be copied? Have you tried your code anywhere or is it just your
speculation?
But the following does *not* work

vector<ifstream> v{ ifstream("file1.txt"), ifstream("file2.txt") };

Because the const is not casted away, this forces vector to try and copy the
elements, but streams are non-copyable. I think this either shows

- A problem with the wording of initializer_list creation (the backing-up
array is non-const but is inteded to be const?)
- Missing wording that you are not allowed to modify the backing array of an
initializer_list (effectively forbidding moving things out of it)
- Something we can apply to Standard containers to make them more widely
usable with initializer lists.
- A poorly designed initializer_list interface: Why are you allowed to move
things out, but you would need an explicit cast? Either disallow it or
explicitly allow it and make begin/end return "T*".

This is my personal opinion, of course. Please let me hear what you think
about it.

V
 
N

Niels Dekker - no reply address

Victor said:
OK, hold on. The wording of [dcl.init.list/4] is:
"An object of type std::initializer_list<E> is constructed from an
initializer list as if the implementation
allocated an array of N elements of type E, where N is the number of
elements in the initializer list.
Each element of that array is copy-initialized with the corresponding
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
element of the initializer list, .. "

Now, how do you suppose the elements are copy-initialized if the
streams cannot be copied? Have you tried your code anywhere or is it
just your speculation?


FWIW, I certainly would appreciate move semantics for initializer_list.
IIUC, it's intended to be reconsidered /after/ C++0x. Rodrigo Castro Campos
wrote a proposal on the issue, "Initializer lists and move semantics",
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2801.pdf

My two cents, Niels
 
J

Johannes Schaub (litb)

Victor said:
Victor said:
On 9/16/2010 8:52 AM, Johannes Schaub (litb) wrote:
It seems that the draft C++0x allows a container to act in the
following way:

template<typename T>
struct container {
/* t's backing array is a T[]. Wrap into move iterators to
* make use of move-construction. */
container(initializer_list<T> t)
:v(make_move_iterator((T*)t.begin()),
make_move_iterator((T*)t.end()))
{

}

std::vector<T> v;
};

Notice that i casted the return tpe of t.begin() and t.end() from "T
const*" to "T*", but that seems to be valid. Any comment?

Could you elaborate on why do you need to cast? Thanks!

The above class can be used to do the following

container<T> c{ ifstream("file1.txt"), ifstream("file2.txt") };

And then you have "c.v" a vector of size 2 with those ifstream elements.
A move_iterator returns "T&&" from its "operator*" such that when it
iterates over a "ifstream"-sequence it returns "ifstream&&" which can be
moved from. If I wouldn't have casted, it would return "ifstream
const&&", which cannot be moved from (it's const!).

OK, hold on. The wording of [dcl.init.list/4] is:
"An object of type std::initializer_list<E> is constructed from an
initializer list as if the implementation
allocated an array of N elements of type E, where N is the number of
elements in the initializer list.
Each element of that array is copy-initialized with the corresponding
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
element of the initializer list, .. "

Now, how do you suppose the elements are copy-initialized if the streams
cannot be copied? Have you tried your code anywhere or is it just your
speculation?

I don't see why you think I would speculate. I already stated that my post
is based on the C++0x draft. This is a copy-initialization:

ifstream a = ifstream();

And it will move the ifstream since it's an rvalue and "a" has a move
constructor.
 
Ad

Advertisements

S

SG

I think this either shows

- A problem with the wording of initializer_list creation (the
backing-up array is non-const but is inteded to be const?)

I *do* think it's intended to be const. Section 8.5.4 paragraph 5
specifically allows a compiler to allocate/initialize these objects
statically like string literals (if possible). So, casting away
constness and modifying the objects is a bad idea.
- Missing wording that you are not allowed to modify the backing
array of an initializer_list (effectively forbidding moving
things out of it)
- Something we can apply to Standard containers to make them more
widely usable with initializer lists.
- A poorly designed initializer_list interface: Why are you allowed
to move things out, but you would need an explicit cast?

What makes you think you are allowed to do that? Just because
paragraph 4 contains the following example?

" struct X {
X(std::initializer_list<double> v);
};
X x{ 1,2,3 };

The initialization will be implemented in a way roughly
equivalent to

double __a[3] = {double{1},double{2},double{3}};
X x (std::initializer_list<double>(__a,__a+3));

assuming the implementation can construct an initializer_list
with a pair of pointers. "

I don't think this proves that moving is allowed, especially because
this is just an explanation for an example with the wording "roughly
equivalent" in it.

This is my personal opinion, of course. Please let me hear what you think
about it.  

To be honest, I don't like std::initializer_list. In my opinion, it's
primarily for toy examples. "Look, Ma! This is how I can initialize my
vector!" In practice, one usually doesn't fill standard containers
like this. Data comes from user input, files, data bases, network
connections, etc. And for simple lookup tables we can still use
statically initialized C arrays. std::initializer_list also doesn't
play nice with move semantics. This is because std::initializer_list
has reference semantics (it doesn't really own its elements) and also
because the draft specifically allows the compiler to use read-only
memory for the elements if it sees fit. No guarantees are made as to
when the elements really have automatic storage and when not.

Victor said:
Each element of that array is copy-initialized with the
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
corresponding element of the initializer list, .. "

I don't think that "copy-initialization" rules out move construction.
But I admit that this term is confusing.

Cheers!
SG
 
J

Johannes Schaub (litb)

SG said:
I *do* think it's intended to be const. Section 8.5.4 paragraph 5
specifically allows a compiler to allocate/initialize these objects
statically like string literals (if possible). So, casting away
constness and modifying the objects is a bad idea.

But it also says "if an explicit array with the same initializer
could be so allocated.". It seems to me this is only valid for a const T
without mutable members, for which the shown "container" template will not
move (because it will cast from "T const*" to "T const*").
What makes you think you are allowed to do that?

Because I have an array of non-const "T" and the initializer_list is said to
refer to that array. I am allowed to modify them. Unless the Standard
explicitly forbids it.

To be honest, I don't like std::initializer_list. In my opinion, it's
primarily for toy examples. "Look, Ma! This is how I can initialize my
vector!" In practice, one usually doesn't fill standard containers
like this. Data comes from user input, files, data bases, network
connections, etc. And for simple lookup tables we can still use
statically initialized C arrays. std::initializer_list also doesn't
play nice with move semantics. This is because std::initializer_list
has reference semantics (it doesn't really own its elements) and also
because the draft specifically allows the compiler to use read-only
memory for the elements if it sees fit. No guarantees are made as to
when the elements really have automatic storage and when not.

I will agree to the "I don't like std::initializer_list". :)
 
V

Victor Bazarov

I don't think that "copy-initialization" rules out move construction.
But I admit that this term is confusing.

No, I didn't mean that. I meant that if copy-initialization is allowed
for creating that array behind the initializer_list<> *and* it is
possible with streams (which aren't copy-initializable, as I understand
it), then there should be no need to const-cast. Otherwise, if the
streams aren't copy-initializable, then their use thus is precluded in
an initializer_list *as well*.

V
 
J

Johannes Schaub (litb)

Victor said:
No, I didn't mean that. I meant that if copy-initialization is allowed
for creating that array behind the initializer_list<> *and* it is
possible with streams (which aren't copy-initializable, as I understand
it), then there should be no need to const-cast. Otherwise, if the
streams aren't copy-initializable, then their use thus is precluded in
an initializer_list *as well*.
They are not "CopyConstructible", using the definition of that requirement
in the Standard, because you cannot direct-initialize a stream from another
const stream:

const ifstream a("file.txt");
(ifstream(a)); // not CopyConstructible

What I meant with my collegial meaning of "copyable" was "CopyConstructible
+ Assignable", which ifstream certainly isn't. A class object is
conceptionally said to be copied by copy-assignment operator and copy-
constructors. Thus the word "copy" does in general not include moving. But
"copy" != "copy-initialize".

To "copy-initialize" just means that you initialize an object using certain
semantics - of which the most important is that only non-explicit
constructors are allowed/considered (for list-initialization, they are
considered but not allowed. For others they are not considered in the first
place). Copy-initialization semantics apply for parameter passing, return-
values and "= foo" initialization. It does not imply that only copy
constructors are allowed to be used, despite the name.

Please consult 8.5 about what "copy-initialization" means.
 
S

SG

No, I didn't mean that.  I meant that if copy-initialization is allowed
for creating that array behind the initializer_list<> *and* it is
possible with streams (which aren't copy-initializable, as I understand
it),

But their move constructors are implicit. That makes them "copy-
initializable" given rvalue initializers, doesn't it? Are we talking
about the same thing? I'm still not sure whether you confuse "copy-
initialization" with "copy-construction" or not.
then there should be no need to const-cast.

Is is possible that you're not familiar with the std::initializer_list
specification? Last time I checked, std::initializer_list<T>::begin/
end both yielded pointers of type const T*.

Cheers!
SG
 
Ad

Advertisements

V

Victor Bazarov

They are not "CopyConstructible", using the definition of that requirement
in the Standard, because you cannot direct-initialize a stream from another
const stream:

const ifstream a("file.txt");
(ifstream(a)); // not CopyConstructible

What I meant with my collegial meaning of "copyable" was "CopyConstructible
+ Assignable", which ifstream certainly isn't. A class object is
conceptionally said to be copied by copy-assignment operator and copy-
constructors. Thus the word "copy" does in general not include moving. But
"copy" != "copy-initialize".

To "copy-initialize" just means that you initialize an object using certain
semantics - of which the most important is that only non-explicit
constructors are allowed/considered (for list-initialization, they are
considered but not allowed. For others they are not considered in the first
place). Copy-initialization semantics apply for parameter passing, return-
values and "= foo" initialization. It does not imply that only copy
constructors are allowed to be used, despite the name.

Huh? Are you saying that somehow, if I *delete* or *make private* my
copy constructor, I will *still* be able to "copy-initialize" an object
of my class, yet not _copy-construct_ it? And please limit your
deductions to classes only, we don't care for built-in types here.
Please consult 8.5 about what "copy-initialization" means.

I will, as soon as you point me to the relevant paragraph which makes it
clear how "copy-initializable" is possible if the class does not fit the
copy-constructible requirement (has no [accessible] copy-constructor).
Thank you.

V
 
S

SG

Huh?  Are you saying that somehow, if I *delete* or *make private* my
copy constructor, I will *still* be able to "copy-initialize" an object
of my class, yet not _copy-construct_ it?  And please limit your
deductions to classes only, we don't care for built-in types here.

The term "copy initialization" doesn't imply any copying (anymore).
It's just a mode of initialization that may also lead to an actual
move constructions or no copy/move construction at all in case they
are elided.

string foo ("blah"); // "direct initialization"
string bar = "blah"; // "copy initialization"

The big difference here is that in the latter case only implicit
constructors are considered and the target class type needs to be copy-
constructible OR move-constructible (or both, but this copy/move is
usually elided). We still call this kind of initialization "copy
initialization" even for move-only types. This is the standardeese
terminology which is, admittedly, a bit confusing considering movable
types.

Cheers!
SG
 
V

Victor Bazarov

The term "copy initialization" doesn't imply any copying (anymore).
It's just a mode of initialization that may also lead to an actual
move constructions or no copy/move construction at all in case they
are elided.

string foo ("blah"); // "direct initialization"
string bar = "blah"; // "copy initialization"

The big difference here is that in the latter case only implicit
constructors are considered and the target class type needs to be copy-
constructible OR move-constructible (or both, but this copy/move is
usually elided). We still call this kind of initialization "copy
initialization" even for move-only types. This is the standardeese
terminology which is, admittedly, a bit confusing considering movable
types.

Can you copy-initialize a stream? Is this valid code:

std::ifstream in = "filename.ext";

?

V
 
V

Victor Bazarov

But their move constructors are implicit. That makes them "copy-
initializable" given rvalue initializers, doesn't it? Are we talking
about the same thing? I'm still not sure whether you confuse "copy-
initialization" with "copy-construction" or not.


Is is possible that you're not familiar with the std::initializer_list
specification? Last time I checked, std::initializer_list<T>::begin/
end both yielded pointers of type const T*.

Yes, that's my mistake. Since the object is const, one would need to
const_cast it, I suppose. But this is (a) unclean to begin with, and
(b) not necessarily based on a working premise (I am still unsure that
there can be an array of streams).

V
 
A

Alf P. Steinbach /Usenet

* SG, on 16.09.2010 20:55:
But their move constructors are implicit. That makes them "copy-
initializable" given rvalue initializers, doesn't it? Are we talking
about the same thing? I'm still not sure whether you confuse "copy-
initialization" with "copy-construction" or not.

Could you quote definitions, please.


Cheers,

- Alf
 
Ad

Advertisements

J

Johannes Schaub (litb)

Victor said:
Can you copy-initialize a stream? Is this valid code:

std::ifstream in = "filename.ext";

It's not valid in C++03, but it is valid in C++0x because streams are
movable: The thing that happens in C++0x and C++03 is that first the char
array is implicitly converted to "ifstream" and yields an ifstream
temporary. Then the "in" object is direct-initialized by that temporary.

For a copy-initialization example that does, in fact, not copy anything:

int n = 0;
int &rn = n; // int reference is copy-initialized.
 
V

Victor Bazarov

It's not valid in C++03, but it is valid in C++0x because streams are
movable: The thing that happens in C++0x and C++03 is that first the char
array is implicitly converted to "ifstream" and yields an ifstream
temporary. Then the "in" object is direct-initialized by that temporary.

OK, so the limitation that you run into is that 'begin' returns a
pointer to const (instead of some kind of special iterator). It would
be nice if it returned something that would have a dereference operator
returning an r-value reference, yes?
For a copy-initialization example that does, in fact, not copy anything:

int n = 0;
int&rn = n; // int reference is copy-initialized.

Let's overlook the fact that it has nothing to do with
std::initializer_list<std::ifstream>, which makes an array of objects,
not references. So, you are suggesting that the 'initializer_list' has
the underlying array in which move-construction is used (not
copy-construction) for the copy-initialization of elements, yes?

V
 
S

SG

Can you copy-initialize a stream?

In C++98: No.
In C++0x: Yes.
Is this valid code:

     std::ifstream in = "filename.ext";

Is std::ifstream's constructor that takes a const char* implicit?
The answer to both questions is "No."

Another example regarding your first question:

std::ifstream in1 ("filename.ext");
std::ifstream in2 = std::move(in1); // <-- !!!

The last line is a "copy initialization". But that doesn't mean stream
in1 is copied to in2. In this case, it is moved. We still call this
"copy initialization".
Could you quote definitions, please.

*sigh*

8.5/14:
"The initialization that occurs in the form

T x = a;

as well as argument passing, function return, throwing an
exception, handling an exception and aggregate member
initializations is called copy-initialization.
[Note: copy-initialization may invoke a move -- end note]"


Cheers!
SG
 
Ad

Advertisements

J

Johannes Schaub (litb)

Victor said:
OK, so the limitation that you run into is that 'begin' returns a
pointer to const (instead of some kind of special iterator). It would
be nice if it returned something that would have a dereference operator
returning an r-value reference, yes?

Certainly. I thought about rvalue ref qualifiers:

move_iterator<T*> begin() && { return __begin; }
T const* begin() const& { return __begin; }

But it won't work that way because it's an lvalue again when it happens to
sit in the constructor's parameter :/

You can overload the constructor though

template<typename T>
struct container {
container(initializer_list<T>&& i) { /* move ... */ }
container(initializer_list<T> const&i) { /* copy ... */ }
};

But it looks a bit weird when seen together with the copy behavior of
initializer_list (no deep copy).

Let's overlook the fact that it has nothing to do with
std::initializer_list<std::ifstream>, which makes an array of objects,
not references. So, you are suggesting that the 'initializer_list' has
the underlying array in which move-construction is used (not
copy-construction) for the copy-initialization of elements, yes?

The initializer_list refers to it. It does not contain it. If you copy an
initializer_list, you just copy pointers, not the real elements (see above).
The draft guarantees that the initializer_list refers to that non-const
array, if "T" is nonconstant (by the text you quoted earlier from 8.5.4/4).
 

Top