Moving elements out of an initializer-list

  • Thread starter Johannes Schaub (litb)
  • Start date
S

SG

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).

The problem with this approach is that std::initializer_list only
*refers* to an array, it doesn't own it. So, from a conceptual point
of view it acts as a pair of iterators rather than a container.
Whether you get your initializer_object as an lvalue or rvalue is of
no significance because you can simply copy it and the copy refers to
the same array.

container(initializer_list<T> const& i)
{
initializer_list<T> j = i; // non-const copy
}

This reminds me of the futile attempts of learning C++ users to get
away with only one iterator type that is supposed to magically serve
two purposes (const iterator and non-const iterator). The error is to
use one type where two distinct types are needed.
[...]
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).

How can it guarantee that it refers to a non-const array when at the
same time it specifically allows a compiler to place the objects into
a ROM?

Cheers!
SG
 
J

Johannes Schaub (litb)

SG said:
[...]
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).

How can it guarantee that it refers to a non-const array when at the
same time it specifically allows a compiler to place the objects into
a ROM?

Well as I pointed out in my earlier response, it does not do so. All it has
is a note (!) that it may place it into a ROM when an explicit array may be
placed that way too. A non-const array cannot be placed into ROM so the
initializer_list won't do so either.
 
S

SG

SG said:
On 16 Sep., 22:24, Johannes Schaub wrote:
[...]
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).
How can it guarantee that it refers to a non-const array when at the
same time it specifically allows a compiler to place the objects into
a ROM?

Well as I pointed out in my earlier response, it does not do so. All it has
is a note (!) that it may place it into a ROM when an explicit array may be
placed that way too. A non-const array cannot be placed into ROM so the
initializer_list won't do so either.

By that logic this optimization would only apply in cases where we
have a constructor like this:

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

(Note the "const double"). I don't believe this is the *intention* of
the draft. From what I can tell the wording is contradictory / worth
improving:

8.5.4/4:

"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, and the std::initializer_list<E> object is
constructed to refer to that array. If a narrowing conversion is
required to initialize any of the elements, the program is
ill-formed. [ 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 this:

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

assuming that the implementation can construct an
initializer_list object with a pair of pointers. --end ]"

8.5.4/5:

"The lifetime of the array is the same as that of the
initializer_list object. [ Example:

typedef std::complex<double> cmplx;
std::vector<cmplx> v1 = { 1, 2, 3 };
void f() {
std::vector<cmplx> v2{ 1, 2, 3 };
std::initializer_list<int> i3 = { 1, 2, 3 };
}

For v1 and v2, the initializer_list object and array created for
{ 1, 2, 3 } have full-expression lifetime. For i3, the
initializer_list object and array have automatic lifetime.
--end example ] [ Note: The implementation is free to allocate
the array in read-only memory if an explicit array with the same
initializer could be so allocated. --end note ]

18.9/2:

"An object of type initializer_list<E> provides access to an
array of objects of type const E. [ Note: A pair of pointers or
a pointer plus a length would be obvious representations for
initializer_list. initializer_list is used to implement
initializer lists as specified in 8.5.4. Copying an initializer
list does not copy the underlying elements. --end note ]"

So, 8.5.4/4 links an initializer_list<E> object to an array of N
elements of type E. 18.9/2 links an initializer_list<E> object to an
array of N elements of type CONST E. Contradiction. Also, I firmly
believe that it is NOT intentional to only allow this ROM optimization
to be used in case E is itself const. I find it much more likely that
someone forgot to add a couple of "const"s in 8.5.4/4.

Cheers!
SG
 
J

Johannes Schaub (litb)

SG said:
SG said:
On 16 Sep., 22:24, Johannes Schaub wrote:
[...]
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).
How can it guarantee that it refers to a non-const array when at the
same time it specifically allows a compiler to place the objects into
a ROM?

Well as I pointed out in my earlier response, it does not do so. All it
has is a note (!) that it may place it into a ROM when an explicit array
may be placed that way too. A non-const array cannot be placed into ROM
so the initializer_list won't do so either.

By that logic this optimization would only apply in cases where we
have a constructor like this:

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

(Note the "const double"). I don't believe this is the *intention* of
the draft. From what I can tell the wording is contradictory / worth
improving:

8.5.4/4:

"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, and the std::initializer_list<E> object is
constructed to refer to that array. If a narrowing conversion is
required to initialize any of the elements, the program is
ill-formed. [ 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 this:

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

assuming that the implementation can construct an
initializer_list object with a pair of pointers. --end ]"

8.5.4/5:

"The lifetime of the array is the same as that of the
initializer_list object. [ Example:

typedef std::complex<double> cmplx;
std::vector<cmplx> v1 = { 1, 2, 3 };
void f() {
std::vector<cmplx> v2{ 1, 2, 3 };
std::initializer_list<int> i3 = { 1, 2, 3 };
}

For v1 and v2, the initializer_list object and array created for
{ 1, 2, 3 } have full-expression lifetime. For i3, the
initializer_list object and array have automatic lifetime.
--end example ] [ Note: The implementation is free to allocate
the array in read-only memory if an explicit array with the same
initializer could be so allocated. --end note ]

18.9/2:

"An object of type initializer_list<E> provides access to an
array of objects of type const E. [ Note: A pair of pointers or
a pointer plus a length would be obvious representations for
initializer_list. initializer_list is used to implement
initializer lists as specified in 8.5.4. Copying an initializer
list does not copy the underlying elements. --end note ]"

So, 8.5.4/4 links an initializer_list<E> object to an array of N
elements of type E. 18.9/2 links an initializer_list<E> object to an
array of N elements of type CONST E. Contradiction. Also, I firmly
believe that it is NOT intentional to only allow this ROM optimization
to be used in case E is itself const. I find it much more likely that
someone forgot to add a couple of "const"s in 8.5.4/4.

I think that it's time for an issue report then. :)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,731
Messages
2,569,432
Members
44,835
Latest member
KetoRushACVBuy

Latest Threads

Top