Is std::initializer_list<T> a literal type?

417 views
Skip to first unread message

dyp

unread,
Dec 16, 2014, 7:38:15 PM12/16/14
to std-dis...@isocpp.org
According to some comments by Richard Smith, std::initializer_list is
required to be a literal type "as of N3471":

https://groups.google.com/a/isocpp.org/d/msg/std-discussion/VAFqmwOtqZo/7yQAqSGw4S8J
http://clang-developers.42468.n3.nabble.com/C-11-constexpr-and-initializer-list-td4031078.html

Originally, I thought this requirement was imposed indirectly, by
specifying that some member functions have to be constexpr. But with
the resolution of CWG DR 1684, this isn't sufficient any more. Is
there another requirement in the Standard (drafts) that
std::initializer_list<T> has to be a literal type?


Thanks and kind regards,

dyp

Richard Smith

unread,
Dec 16, 2014, 8:33:12 PM12/16/14
to std-dis...@isocpp.org
On Tue, Dec 16, 2014 at 4:38 PM, dyp <dyp...@gmx.net> wrote:
According to some comments by Richard Smith, std::initializer_list is
required to be a literal type "as of N3471":

https://groups.google.com/a/isocpp.org/d/msg/std-discussion/VAFqmwOtqZo/7yQAqSGw4S8J
http://clang-developers.42468.n3.nabble.com/C-11-constexpr-and-initializer-list-td4031078.html

Originally, I thought this requirement was imposed indirectly, by
specifying that some member functions have to be constexpr. But with
the resolution of CWG DR 1684, this isn't sufficient any more.

Creation of a std::initializer_list<T> object from a braced init list does not violate any of the rules in [expr.const], whether or not std::initializer_list<T> is a literal type, so it is still a constant expression.


However, there is an open question of whether (for instance) the copy constructor of initializer_list<T> is usable in a constant expression. This is a corner of a much more fundamental problem. The library's specification for 'constexpr' is extremely weak in general. For instance:

 * std::initializer_list<int>'s default constructor need not actually be usable in a constant expression (the standard merely requires that *some* instantiation of the default constructor is usable in a constant expression)

 * pair<int, int>{0, 0} is not guaranteed to be a valid constant expression, because pair<int, int> might not be a literal type.

The only library types that are (normatively) guaranteed to be literal types are complex<float>, complex<double>, and complex<long double>. And even then, there are no guarantees as to what operations will actually work in constant expressions.

Until the library issue is resolved, we're presumably expected to read between the lines: where a library function says it's constexpr, we should assume that any usage of that function that could reasonably be a constant expression is in fact a constant expression. (For some value of "reasonably".)

dyp

unread,
Dec 17, 2014, 11:18:34 AM12/17/14
to std-dis...@isocpp.org
On 12/17/2014 02:33 AM, Richard Smith wrote:
> Creation of a std::initializer_list<T> object from a braced init list does
> not violate any of the rules in [expr.const], whether or not
> std::initializer_list<T> is a literal type, so it is still a constant
> expression.
>
>
> However, there is an open question of whether (for instance) the copy
> constructor of initializer_list<T> is usable in a constant expression.

[dcl.init.list]/3.5 seems to imply that the creation of any named
std::initializer_list<T> object, e.g. as a function parameter or a
variable, requires a constexpr copy/move constructor.

The _literal type_ property, as far as I understand it, is a separate
issue; is required for a constexpr object declaration like:

#include <initializer_list>
int main() {
constexpr std::initializer_list<int> l = {1,2,3,4};
}

Which does not compile on clang++ tip-of-trunk, but I guess that's a bug (?)

> where a library function says it's constexpr, we should
> assume that any usage of that function that could reasonably be a constant
> expression is in fact a constant expression.

This does not seem to imply that std::initializer_list<T> shall be a
literal type: It can be created "magically" via list-initialization
within a constant expression (without the restrictions of a constexpr
ctor), then I can call its constexpr member functions, and those might
even access data members of the class - as far as I understand even if
the class is not a literal type. Example:

#include <initializer_list>
constexpr auto x = *std::initializer_list{1,2,3}.begin();
int main() {}

This by the way compiles with g++4.9 but not clang++ tip-of-trunk, while

#include <initializer_list>
#include <utility>

template<typename T>
constexpr decltype(auto)
make_xvalue_init_list(std::initializer_list<T>&& rvalue)
{ return std::move(rvalue); }

constexpr auto x = *make_xvalue_init_list({1,2,3}).begin();

int main() {}

compiles with both.

Richard Smith

unread,
Dec 17, 2014, 2:28:53 PM12/17/14
to std-dis...@isocpp.org
On Wed, Dec 17, 2014 at 8:18 AM, dyp <dyp...@gmx.net> wrote:
On 12/17/2014 02:33 AM, Richard Smith wrote:
> Creation of a std::initializer_list<T> object from a braced init list does
> not violate any of the rules in [expr.const], whether or not
> std::initializer_list<T> is a literal type, so it is still a constant
> expression.
>
>
> However, there is an open question of whether (for instance) the copy
> constructor of initializer_list<T> is usable in a constant expression.

[dcl.init.list]/3.5 seems to imply that the creation of any named
std::initializer_list<T> object, e.g. as a function parameter or a
variable, requires a constexpr copy/move constructor.

That's fair; initializer_list seems underspecified in this regard.

The _literal type_ property, as far as I understand it, is a separate
issue; is required for a constexpr object declaration like:

#include <initializer_list>
int main() {
    constexpr std::initializer_list<int> l = {1,2,3,4};
}

Which does not compile on clang++ tip-of-trunk, but I guess that's a bug (?)

This is not a bug, per the current standard. The above is comparable to:

  struct init_list_int { const int *begin, *end; };
  int main() {
    constexpr int arr[] = { 1, 2, 3, 4 };
    constexpr init_list_int l = { arr, arr + 4 };
  }

which is ill-formed because the constexpr object l contains a pointer to an automatic storage duration object (its value is not constant because it contains a pointer to the stack).

The above code would be valid if the initializer_list object were declared 'static'.

> where a library function says it's constexpr, we should
> assume that any usage of that function that could reasonably be a constant
> expression is in fact a constant expression.

This does not seem to imply that std::initializer_list<T> shall be a
literal type: It can be created "magically" via list-initialization
within a constant expression (without the restrictions of a constexpr
ctor), then I can call its constexpr member functions, and those might
even access data members of the class - as far as I understand even if
the class is not a literal type. Example:

#include <initializer_list>
constexpr auto x = *std::initializer_list{1,2,3}.begin();
int main() {}

I agree, but consider an example like this:

  constexpr std::initializer_list<int> il;

In marking the initializer_list constructor as constexpr, the library -- presumably -- intends to imply that code is valid, which in turn requires that initializer_list<int> is a literal type. But this is all reading between the lines; I've asked for a library issue to clarify all this.
 
This by the way compiles with g++4.9 but not clang++ tip-of-trunk,

(You're missing an <int>.) Yeah, that was a Clang bug (now fixed). Thanks for the report.
Reply all
Reply to author
Forward
0 new messages