1

I am implementing a class in c++17 that needs to be able to construct an object at compile time using a constexpr constructor. The object has an array member which I cannot seem to be able to initialize through an argument.

I've been trying to use std::initializer_list for this, something along the lines of:

#include <cstdint>
#include <initializer_list>

struct A {
 char data[100];

 constexpr A(std::initializer_list<char> s): data(s) {}
};

int main() {
  constexpr A a{"abc"};
}

But this doesnt work for some reason.

clang 6.0 (also tried later versions) tells me I'm wrong although I'm doing what it says:

test_initializer_list.cpp:7:46: error: array initializer must be an initializer list or string literal
 constexpr A(std::initializer_list<char> s): data(s) {}

(Note that if I gives it a string literal directly, like data("abc"), it does compile)

g++ (7.5.0) says that I cannot use an initializer list for some reason - but if so, what can I use?

test_initializer_list.cpp:7:52: error: incompatible types in assignment of 'std::initializer_list<char>' to 'char [100]'
  constexpr A(std::initializer_list<char> s): data(s) {}
                                                    ^

Am I doing something wrong?

3
  • Does this answer your question? How do I initialize a member array with an initializer_list? Commented Jun 11, 2020 at 8:30
  • What makes you think that "abc" is (or can be converted into) std::initializer_list<char>? Commented Jun 11, 2020 at 8:33
  • @dfri - Thanks but not exactly, because they don't need constexpr @Evg - you are right in principal, but trying to put there {'a', 'b', 'c'} doesn't work either. Commented Jun 11, 2020 at 11:57

1 Answer 1

3

The explanation is going to be long, but bear with me.


You can't initialize an array with an std::initializer_list.

When you write something like int arr[] = {1,2,3}, the part enclosed in braces is not an std::initializer_list.

If you don't believe me, consider a similar initialization for a structure:

struct A {int x; const char *y;};
A a = {1, "2"};

Here, {1, "2"} can't possibly be an std::initializer_list, because the elements have different types.

The C++ grammar calls those brace-enclosed lists braced-init-lists.

std::initializer_list is a magical class (not implementable in standard C++, that is) that can be constructed from a braced-init-list.

As you noticed, std::initializer_list can't be used in place of a braced-init-list. "Braced-init-list" refers to a specific grammatical construct, so an initializer for an array (e.g. in a member init list) must literally be a brace-enclosed list. You can't save this list to a variable, and initialize an array with it later.

constexpr A() : data{'1','2','3'} {} // Valid
constexpr A(initializer_list<char>/*or whatever*/ list) : data{list} {} // Not possible

A possible solution is to use a loop to copy elements from std::initializer_list into the array.

Since the constructor is constexpr, the array must be initialized with something; initialize it with zeroes using : data{}:

constexpr A(std::initializer_list<char> s) : data{} {/*copy elements here*/}

Now A a({'1','2','3'}); will work. But A a("123"); still won't.

A string literal ("123") is neither a braced-init-list nor an std::initializer_list, and it can't be converted to them.

There's a special rule for initializing char arrays with string literals (char x[] = "123";). In addition to a braced-init-list, the grammar allows a string literal to be used there.

It has to be a string literal, i.e. a string enclosed in quotes; a const char * variable or an another array of char won't do.

If you want A a("123"); to be valid, the parameter of the constructor needs to be a const char * (there are some other options, but they don't help much here). Use a loop to copy the characters from the pointed memory into the array. Don't forget to initialize the array with zeroes (: data{}), because your constructor is constexpr.

There's a third option: replace char data[100] with an std::array, and also make the parameter of the constructor an std::array. Unlike plain arrays, those can be copied, so : data(list) will work. And std::array can be initialized with both a braced-init-list (A a({'1','2','3'});), and a brace-enclosed string literal (A a({"123"});).

And there's yet another option you have: remove the constructor. Your struct will then become an aggregate (simply saying, a struct that has no custom constructors and can be initialized member-wise with a braced-init-list). Then both A a{{'1','2','3'}};, A a{'1','2','3'}; (braces can be omitted), and A a{"123"}; will work.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for the explanation! I guess I had a feeling about copying in the function body solution (though for constexpr this is more painful) but had no idea about braced-init-list vs initialization-list not being the same which was frustrating. Thanks also for the std::array idea!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.