I have a very simple structure that looks like this:
struct Arr
{
static constexpr auto width = 2;
static constexpr auto height = 2;
std::array<std::array<int, width>, height> values;
};
How do I properly initialize this structure without getting compiler errors or warnings?
I tried the following (see example on Compiler Explorer):
auto arr = Arr{1, 2, 3, 4}; // clang: warning: suggest braces around initialization of subobject [-Wmissing-braces]
auto arr = Arr{{1, 2}, {3, 4}}; // clang: error: excess elements in struct initializer
auto arr = Arr{{{1, 2}, {3, 4}}}; // clang: error: excess elements in struct initializer
I know there have been similar questions asked (e.g. Nested aggregate initialization of std::array), but it seems all of them focus on C++11/14.
Related
When initializing a 2D array we can do
int data[2][2] = {{1, 2}, {3, 4}}; // OK
we can also use
int data[2][2] = {1, 2, 3, 4}; // OK
which makes sense since a 2D array still is a continues chunk of memory.
When using the array class instead of the base type, this works
array<array<int, 2>, 2> data = {1, 2, 3, 4}; // OK
Which again makes sense, since array class doesn't have any extra data and ends up as a continues chunk of memory as well.
But the 2D list does not work:
array<array<int, 2>, 2> data = {{1, 2}, {3, 4}}; // Error
I can also initialize this
vector<array<int, 2>> data = {{1, 2}, {3, 4}}; // OK
But couldn't find anyway to initialize:
array<vector<int>, 2> = ????
My question is:
Is there a fundamental design reason for this (My guess is something related to stuff happening at compile-time vs run-time)? Or is this just an implementation decision for the compiler I am using (GCC)?
std::array doesn't have any user-defined constructor (like std::vector), it just contains a underlying array, when performing aggregate initialization you need one more braces for it.
array<array<int, 2>, 2> data = {{{{1, 2}}, {{3, 4}}}};
// ^ ^ <- for array<array<int, 2>, 2>
// ^ ^ <- for the underlying array
// ^ ^ <- for the 1st array<int, 2>
// ^ ^ <- for its underlying array
// ^ ^ <- for the 2nd array<int, 2>
// ^ ^ <- for its underlying array
We can omit braces as
array<int, 2> data = {1, 2};
array<array<int, 2>, 2> data = {1, 2, 3, 4};
because of brace elision:
The braces around the nested initializer lists may be elided (omitted), in which case as many initializer clauses as necessary are used to initialize every member or element of the corresponding subaggregate, and the subsequent initializer clauses are used to initialize the following members of the object.
That means the above code code be written as
array<array<int, 2>, 2> data = {{{1, 2}, {3, 4}}};
And array<array<int, 2>, 2> data = {{1, 2}, {3, 4}}; fails because it's interpreted as:
array<array<int, 2>, 2> data = {{1, 2}, {3, 4}};
// ^ ^ <- for array<array<int, 2>, 2>
// ^ ^ <- for the underlying array
// ^ ^ <- excess elements; std::array has only one underlying array
Consider the following code:
#include <ranges>
int main() {
constexpr int a[] = {1, 2, 3, 4};
constexpr auto r = a | std::views::take(3);
static_assert(*r.begin() == 1);
}
msvc accept it, gcc rejects it with:
<source>:5:44: error: 'std::ranges::take_view<std::ranges::ref_view<const int [4]> >{3, std::ranges::ref_view<const int [4]>{(& a)}}' is not a constant expression
5 | constexpr auto r = a | std::views::take(3);
| ^
Why does r is not a constant expression?
The ranges bit is a red herring. It can be boiled down to this
int main() {
constexpr int a[] = {1, 2, 3, 4};
constexpr auto r = a ;
}
We cannot form a constexpr pointer to the first element of a. Such pointers are constrained to hold only the addresses of objects with static storage duration. The view object (as part of its implementation somewhere deep down) is going to need to keep a copy of an iterator (the pointer in our case).
Since r is declared constexpr and it internally holds a pointer to an object, that object too must have static storage duration. Indeed, modifying the code to contain
static constexpr int a[] = {1, 2, 3, 4};
Makes GCC accept your example happily.
MSVC is non-conformant however. It accepts the invalid plain pointer example as well.
I often use initializer lists and for-each loops to iterate through a small number of ad-hoc values, like so:
for (auto x : {1, 2, 6, 24, 120}) {
do_something(x);
}
I recently tried to write something similar, but with structured bindings and packed-together values instead:
for (auto[dx, dy] : {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}) {
try_to_move(dx, dy); // nope! won’t compile
}
Unfortunately, this doesn’t compile. Clang tells me:
error: cannot use type ‘void’ as a range
In fact, even something like auto mylist = {{1, 2}, {3, 4}}; won’t compile.
This leaves me with two questions:
Is there an alternative syntax to accomplish what I want in a terse and readable manner?
Why doesn’t the type of auto mylist get parsed as initializer_list<initializer_list<int>>? Wouldn’t that work fine?
Why doesn’t the type of auto mylist get parsed as initializer_list<initializer_list<int>>? Wouldn’t that work fine?
The reason is that simply no-one proposed it yet.
The handy syntax auto x = {1, 2, 6, 24, 120}; comes from proposal N3912 which was adopted into C++17 (see also N3922).
The deduction process is outlined in [dcl.type.auto.deduct]/4:
If the placeholder is the auto type-specifier, the deduced type T' replacing T is determined using the rules for template argument deduction. Obtain P from T by replacing the occurrences of auto with either a new invented type template parameter U or, if the initialization is copy-list-initialization, with std::initializer_list<U>. Deduce a value for U using the rules of template argument deduction from a function call, where P is a function template parameter type and the corresponding argument is e. If the deduction fails, the declaration is ill-formed.
Since type deduction in a hypothetical function call f({1, 2}) would fail, so too is the nested braced-init-list deduction auto x = { {1, 2}, {3, 4} }; also impossible.
I guess the same trick could be applied to a deduction from a function call, which would make nested initializer_list deduction possible.
So a follow-up proposal is welcome.
Is there an alternative syntax to accomplish what I want in a terse and readable manner?
You could define a good old multidimensional array:
int lst[][2] = { {1, 2}, {3, 4}, {5, 6}, {7, 8} };
for (auto [x, y] : lst) {
. . .
}
Or as suggested in the comments, give the first pair a type to help the deduction:
for (auto [x, y] : { std::pair{1, 2}, {3, 4}, {5, 6}, {7, 8} }) {
. . .
}
You can use it like this. You just need to initialize your ad-hoc list to a variable first.
vector<pair<int, int> > p = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
for (auto x : p) {
try_to_move(x);
}
You then access the parameter x like this in the function
<return-type> try_to_move(pair<int, int> x){
int dx = x.first;
int dy = x.second;
// TO-DO
}
Suppose I have an array of:
std::array<int, 6> {4,3,2};
Is it possible to raise an error or warning when this is the case? In some cases it might be useful to have this explicitly matching.
You can use std::make_array or something like it to cause the types to differ
std::array<int, 6> = std::make_array(4,3,2);
gives this error in gcc:
<source>:30:53: error: conversion from 'array<[...],3>' to non-scalar type 'array<[...],6>' requested
You can create your own layer of abstraction that complains when you don't pass the exact same number of arguments for initialization.
template <std::size_t N, class ...Args>
auto createArray(Args&&... values)
{
static_assert(sizeof...(values) == N);
using First = std::tuple_element_t<0, std::tuple<Args...>>;
return std::array<First, N>{values...};
}
To be invoked as
auto ok = createArray<6>(4, 3, 2, 1, 0, -1);
auto notOk = createArray<6>(4, 3, 2};
Instead of writing your own createArray method you can use the https://en.cppreference.com/w/cpp/experimental/make_array
if your compiler supports it.
#include <experimental/array>
int main()
{
std::array<int,5> arr1= std::experimental::make_array(1, 2, 3, 4, 5); // ok
std::array<int,3> arr2= std::experimental::make_array(1, 2, 3, 4, 5); // fails
std::array<int,6> arr3= std::experimental::make_array(1, 2, 3, 4, 5); // fails
}
But everybody can directly init the array via constructor. So there is no guarantee for your code base if you don't check ( automatically ) against some coding guidelines.
I am working with vectors and I am initialising them like so:
vector<int> values;
values.push_back(1);
values.push_back(2);
values.push_back(8);
values.push_back(12);
values.push_back(32);
values.push_back(43);
values.push_back(23);
values.push_back(234);
values.push_back(7);
values.push_back(1);
Is there a way, to push_back these elements in a way that is array-like? Like this:
int numbers[2] = {1, 2};
The vector method takes up too many lines, IMO!
In C++11 you can use brace initialization with any container.
std::vector <int> v = {1, 2, 8, 12, 32 ...};
In C++03 you can do this
const int arr[] = {1, 2, 8, 12, 32 ... };
const int size = sizeof arr / sizeof arr[0];
std::vector<int> v(arr, arr + size);
or use boost assign.
#include <boost/assign.hpp>
using namespace boost::assign;
///...
{
std::vector<int> v;
v += 1, 2, 8, 12, 32;
}
There is indeed a way to do this, provided you have your hands on a C++11 compiler:
std::vector<int> values = {1, 2, 3, 4, 5};
This is known as an initializater list. std::vector has a constructor that takes an initializer list (see declaration 7) to initialise its elements.
In C++11, you can do it the same way you do with arrays. std::vector has a constructor that takes a std::initializer_list<T>, which allows it to initialize itself from the list.
In C++03, you can use a temporary array:
int tempValues[] = {1,2,8,12,32,43,23,234,7,1};
std::vector<int> values(tempValues, tempValues + sizeof tempValues / sizeof tempValues[0]);