Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Unexpected results of auto-deduced constexpr initializer_list

46 views
Skip to first unread message

Adam Badura

unread,
May 16, 2017, 2:48:34 AM5/16/17
to
Let's consider simple example like this:
-----
constexpr auto few_first_primes = {2, 3, 5, 7, 11, 13, 17, 19};
-----
auto deduces here std::initializer_list as expected. What is not expected however is its value type. It is not int but const int!

Following code confirms that:
-----
#include <initializer_list>
#include <type_traits>

constexpr auto few_first_primes = {2, 3, 5, 7, 11, 13, 17, 19};

static_assert(
std::is_same<
decltype(few_first_primes),
std::initializer_list<int const> const
>::value,
"initializer_list holds const ints!"
);
-----

Now when you think about it that seems logical. After all those values are constants, right?

The problem is however, that this makes the few_first_primes value far less usable! In particular, you can no longer use it with standard containers to initialize them or add values. Below code:
-----
std::vector<int> some_primes(few_first_primes);
-----
fails to compile with error:
-----
Test.cpp:15:49: error: could not convert ‘{few_first_primes}’ from ‘<brace-enclosed initializer list>’ to ‘std::vector<int>’
std::vector<int> some_primes = {few_first_primes};
-----
using "{few_first_primes}" or "= {few_first_primes}" initialization instead of "(few_first_primes)" fails to compile as well, however case above shows best error description (for the needs of this discussion).

Also, we get similar error with other functions taking the initializer_list:
-----
std::vector<int> some_primes;
some_primes.insert(some_primes.end(), few_first_primes);
-----

We can still use iterator-based approach:
-----
std::vector<int> some_primes {few_first_primes.begin(), few_first_primes.end()};
-----
but this doesn't seem as nice.

The problem seems to be that standard containers use initializer_list with value type fixed to their own value type. And since int and const int are not the same type we have a problem here.

Why aren't the standard containers more tolerant for the initializer_list value type, just as they are with iterators?

Code samples and error message are based on g++ (GCC) 6.3.0 with compilation flags being "-Wall -Wextra -pedantic -std=c++14".

Alf P. Steinbach

unread,
May 16, 2017, 2:56:32 AM5/16/17
to
On 16-May-17 8:48 AM, Adam Badura wrote:
> Let's consider simple example like this:
> -----
> constexpr auto few_first_primes = {2, 3, 5, 7, 11, 13, 17, 19};
> -----
> auto deduces here std::initializer_list as expected. What is not
> expected however is its value type. It is not int but const int!

That's not unexpected: it's required (for a `std::initializer_list`).

And yes it's a sometimes annoying limitation.

Still initializer lists are great. :)


Cheers!,

- Alf

Adam Badura

unread,
May 16, 2017, 3:23:08 AM5/16/17
to
> > Let's consider simple example like this:
> > -----
> > constexpr auto few_first_primes = {2, 3, 5, 7, 11, 13, 17, 19};
> > -----
> > auto deduces here std::initializer_list as expected. What is not
> > expected however is its value type. It is not int but const int!
>
> That's not unexpected: it's required (for a `std::initializer_list`).
>
> And yes it's a sometimes annoying limitation.

So what prevents the standard containers from being more flexible with the accepted initializer_list types? Just as it is flexible with iterators. Why not change specification in this area?

Alf P. Steinbach

unread,
May 16, 2017, 4:12:26 AM5/16/17
to
I agree that it would be nice with possible non-`const` items of
initializer lists, in particular so that items of class type could just
be `move`d instead of actually copied.

Regarding the rationale for the current spec, the `const`-ness, I'm not
sure.

It could be that if or when someone points that out we'll say “Aha! It
couldn't very well be otherwise, lest one would incur unreasonable
complexity and violate the principle of least surprise.”


Cheers!,

- Alf

Adam Badura

unread,
May 16, 2017, 4:14:56 AM5/16/17
to
OK, so where and how to "point that out"?

Juha Nieminen

unread,
May 17, 2017, 2:56:33 AM5/17/17
to
Adam Badura <adam.f...@gmail.com> wrote:
> The problem is however, that this makes the few_first_primes value
> far less usable! In particular, you can no longer use it with
> standard containers to initialize them or add values. Below code:

I tried this with clang, and it compiled and worked fine:

//-----------------------------------------------------------
#include <vector>
#include <iostream>

int main()
{
const auto values = { 1, 3, 5, 7 };
for(auto value: values) std::cout << value << "\n";
std::vector<int> vec = values;
for(auto value: vec) std::cout << value << "\n";
}
//-----------------------------------------------------------

Curiously, if I change the 'const' to 'constexpr', it fails to compile,
giving the error:

test.cc:7:20: error: constexpr variable 'values' must be initialized by a
constant expression
constexpr auto values = { 1, 3, 5, 7 };
^ ~~~~~~~~~~~~~~

test.cc:7:20: note: pointer to subobject of temporary is not a constant
expression
test.cc:7:29: note: temporary created here
constexpr auto values = { 1, 3, 5, 7 };
^

If I move 'values' to the global scope, then it works as a constexpr.

Ian Collins

unread,
May 17, 2017, 5:24:42 AM5/17/17
to
Interesting... g++ 6.3.0 doesn't mind the use of constexpr, but it
dislikes the vector assignment (with or without constexpr):

g++ x.cc -std=c++14
x.cc: In function 'int main()':
x.cc:9:28: error: conversion from 'const std::initializer_list<const
int>' to non-scalar type 'std::vector<int>' requested
std::vector<int> vec = values;
^~~~~~
--
Ian

Juha Nieminen

unread,
May 22, 2017, 2:31:01 AM5/22/17
to
Ian Collins <ian-...@hotmail.com> wrote:
>> const auto values = { 1, 3, 5, 7 };

> Interesting... g++ 6.3.0 doesn't mind the use of constexpr, but it
> dislikes the vector assignment (with or without constexpr):

I checked, and clang makes 'values' of type std::initializer_list<int>,
while gcc makes it of type std::initializer_list<const int>.

Which one is right? Or are both right?

Ian Collins

unread,
May 22, 2017, 2:49:17 AM5/22/17
to
Um.. The type for the initializer_list underlying array should be const
int, the elements are immutable. I wonder if the compiler implement the
underlying storage differently, it is unspecified,

--
Ian

Juha Nieminen

unread,
May 22, 2017, 4:39:56 AM5/22/17
to
I doubt you can modify the values pointed by std::initializer_list
(at least not standard-legally). std::initializer_list<T>::iterator
is a const T*, and std::initializer_list<T>::reference is const T&.
And those are the only ways to access the elements. The only way you
could try to modify the elements is to cast the constness away, which
I doubt is very kosher.

Thus I would guess that from a standard perspective it doesn't make
a difference whether the element type is int or const int (but I don't
know what the standard says about this.)
0 new messages