itai
itai

Reputation: 1606

C++: Initialize a member array within a constexpr constructor

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?

Upvotes: 1

Views: 1839

Answers (1)

HolyBlackCat
HolyBlackCat

Reputation: 96256

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.

Upvotes: 3

Related Questions