Reputation: 1606
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
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 anstd::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