Initialize a class with brace-enclosed initializer list - c++

I'm trying to initialize a class Vec with a brace-enclosed initializer list, to be called like in this minimal example:
int main()
{
Vec<3> myVec1{{1,2,3}}; // or: myVec1({1,2,3});
Vec<3> myVec2;
myVec2 = {1, 2, 3};
Vec<3> myVec3 = {1,2,3};
return 0;
}
All these initializations (and the assignment) should work. (Plus custom default constructor. Using an aggregate class is thus impossible.)
While I could just use a std::initializer_list like such:
template <unsigned C>
struct Vec
{
Vec(){}
Vec(std::initializer_list<int> list)
{
//does not work for obvious reasons:
//static_assert(list.size() == C, "");
}
};
I can't statically ensure the number of parameters to be equal to my template parameter, i.e., an initialization with {1, 2, 3, 4} would only fail during runtime.
Browsing SO, I came up with the following:
template <unsigned C>
struct Vec
{
Vec(){}
Vec(const unsigned(&other)[C]){}
Vec& operator=(const unsigned(&other)[C]){return *this;}
};
This works fine for the assignment and () or {} initialization (as for myVec1) - but it fails for the initialization using = (as for myVec3).
(GCC gives error "could not convert '{1, 2, 3}' from '' to 'Vec<3u>'")
I don't get why one of the initializations should work, but not the other.
Any other ideas how I can use the brace-enclosed initializer list, but also ensure the correct length at compile time?
Thanks :)

Initializer lists are more appropriate for situations where the size of the list is dynamic. In your case, where the size of Vec is a template parameter (i.e. static), you're probably better off with variadic parameters:
template <unsigned C>
struct Vec
{
Vec(){}
template<typename ... V>
Vec(V ... args)
{
static_assert(sizeof...(V) == C, "");
//...
}
};
then these will work:
Vec<3> myVec2;
myVec2 = {1, 2, 3};
Vec<3> myVec3 = {1,2,3};
But this one won't, as it explicitly requires an std::initializer_list:
Vec<3> myVec1{{1,2,3}};

You need two pair of brackets:
Vec<3> myVec3 = {{1,2,3}};
When you do:
Vec<3> myVec3 = {1, 2, 3};
// It is the same as (except that explicit constructor are not considered):
Vec<3> myVec3 = Vec<3>{1, 2, 3};'
So basically, the compiler will look for a constructor for Vec<3> which either takes an std::initializer_list or a constructor that takes 3 arguments, all of which being constructible from int.
If you want a std::array like structure, you need to make your type an aggregate, meaning that it should not have user-defined constructor:
template <unsigned N>
struct Vec {
int data[N];
};
void f() {
Vec<3> v1{{1, 2, 3}};
Vec<3> v2 = {{1, 2, 3}};
}

Related

Use constructor parameters outside constructor in constexpr class

I am trying to implement matrix-like class, using an std::array to actually store the data. All of the data is known at compile-time.
I want to be able to use initializer-lists to initialize the Matrix. Something along the lines of
Matrix m = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
I also want to be able to set the dimensions of my matrix during instantiation.
My current code is similar to the following:
template<int m, int n>
class Matrix {
public:
using initializer_list2d = std::initializer_list< std::initializer_list<float> >;
using array2d = std::array< std::array<float, m>, n >;
consteval Matrix(initializer_list2d initList) {
// static_assert to check initList length [...]
int i = 0;
for (auto &row : initList) {
// static_assert to check row length [...]
std::copy(row.begin(), row.end(), data_[i].begin());
i++;
}
}
// Definitions of operators and methods [...]
private:
array2d data_;
};
In order to use this though, I have to set the dimensions through the template:
Matrix<3, 3> m = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
It seems to me, that since the constructor is consteval (and all of its parameters have to be known at compile-time anyway) it should somehow bet possible to deduce the dimensions through the initializer-list alone.
Using a dynamic data type (e.g. vector) is not possible for my application, since this program has to be able to run without access to the heap.
Is there some way to achieve this?
Thanks for any help in advance!
Before C++17 there is no deduction of template arguments for a class template, so this is not possible.
From C++17, you can write this deduction guide:
template<int m, int n>
Matrix(float const (&)[n][m]) -> Matrix<m, n>;
and add an extra pair of braces when constructing the Matrix:
Matrix m =
{
{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
}
};
Here's a demo.
I found another interesting solution, using variadic templates:
template<int m, class T, class... U>
struct Matrix {
Matrix(const T (&t)[m], const U (& ...u)[m]) {
std::copy(t, t+m, arr[0]);
int i = 0;
(std::copy(u, u+m, arr[++i]), ...);
}
T (arr[m])[sizeof...(U) + 1];
};
It even allows for getting rid of the second brace required in cigien's answer and doesn't need a template deduction guide (Although it still needs C++17 in order for int m to be automatically deduced):
Matrix mat = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
Also, the compiler seems to be able to optimze this quite a bit, as seen here

How to define a vector class using std::array with aggregate initialization?

I define a class like this
#include<array>
template<class T,size_t D>
class vec{
private:
std::array<T,D> arr;
public:
vec(std::initializer_list<T> l) : arr(l){ }
};
Then I construct the object of this class like this.
vec<int,3> v = {1,2,3};
Then I get an error
main.cpp:5:26: error: cannot bind non-const lvalue reference of type ‘std::initializer_list<int>&’ to an rvalue of type ‘std::initializer_list<int>’
vec<int,3> v = {1,2,3};
^
In file included from main.cpp:1:
vec.hpp:12:9: note: initializing argument 1 of ‘vec<T, D>::vec(std::initializer_list<_Tp>&) [with T = int; long unsigned int D = 3]’
vec(std::initializer_list<T>& l) : arr(l){ }
I wonder how can I make the aggregated initialization in my constructor valid.
A std::array is not constructable from a std::initializer_list. You have two options to fix this if you want to keep the std::array member. You can make vec and aggregate by making arr public and removing the constructor like
#include<array>
template<class T,size_t D>
class vec{
public:
std::array<T,D> arr;
};
int main()
{
vec<int,3> v = {1,2,3};
}
or you keep it as you have it but instead you iterate over arr and assign it values from the initializer list. That would look like
#include<array>
template<class T,size_t D>
class vec{
private:
std::array<T,D> arr;
public:
vec(std::initializer_list<T> l)
{
std::copy_n(l.begin(), std::min(l.size(), arr.size()), arr.begin());
}
};
int main()
{
vec<int,3> v = {1,2,3};
}
This method does require the array members to be default constructable. If you can't/don't want to guarantee that then you need option one or switch to using a different type for arr like std::vector.
std::array does not have any constructors. It has only aggregate initializer. If you replace std::array by std::vector it should works because vector has initializer list constructor.

Can I initialize an array using the std::initializer_list instead of brace-enclosed initializer?

Can I initialize an array using the std::initializer_list object instead of brace-enclosed initializer?
As known, we can do this: http://en.cppreference.com/w/cpp/language/aggregate_initialization
unsigned char b[5]{"abc"};
// equivalent to unsigned char b[5] = {'a', 'b', 'c', '\0', '\0'};
int ar[] = {1,2,3};
std::array<int, 3> std_ar2{ {1,2,3} }; // std::array is an aggregate
std::array<int, 3> std_ar1 = {1, 2, 3};
But I can't initialize an array by std::initializer_list il;:
http://ideone.com/f6aflX
#include <iostream>
#include <initializer_list>
#include <array>
int main() {
int arr1[] = { 1, 2, 3 }; // OK
std::array<int, 3> arr2 = { 1, 2, 3 }; // OK
std::initializer_list<int> il = { 1, 2, 3 };
constexpr std::initializer_list<int> il_constexpr = { 1, 2, 3 };
//int arr3[] = il; // error
//int arr4[] = il_constexpr; // error
//std::array<int, 3> arr5 = il; // error
//std::array<int, 3> arr6 = il_constexpr; // error
return 0;
}
But how can I use std::initializer_list il; to initialize an array?
Other answered correctly said this is not possible upfront. But with little helpers, you can get pretty close
template<typename T, std::size_T N, std::size_t ...Ns>
std::array<T, N> make_array_impl(
std::initializer_list<T> t,
std::index_sequence<Ns...>)
{
return std::array<T, N>{ *(t.begin() + Ns) ... };
}
template<typename T, std::size_t N>
std::array<T, N> make_array(std::initializer_list<T> t) {
if(N > t.size())
throw std::out_of_range("that's crazy!");
return make_array_impl<T, N>(t, std::make_index_sequence<N>());
}
If you are open to more work arounds, you can put this into a class to catch statically-known length violations for the cases where you pass a braced init list. But be warned that most people who read this code will head-desk
template<typename T, std::size_t N>
struct ArrayInitializer {
template<typename U> struct id { using type = U; };
std::array<T, N> t;
template<typename U = std::initializer_list<T>>
ArrayInitializer(typename id<U>::type z)
:ArrayInitializer(z, std::make_index_sequence<N>())
{
if(N > z.size())
throw std::out_of_range("that's crazy!");
}
template<typename ...U>
ArrayInitializer(U &&... u)
:t{ std::forward<U>(u)... }
{ }
private:
template<std::size_t ...Ns>
ArrayInitializer(std::initializer_list<T>& t,
std::index_sequence<Ns...>)
:t{ *(t.begin() + Ns) ... }
{ }
};
template<typename T, std::size_t N>
std::array<T, N> f(ArrayInitializer<T, N> ai) {
return std::move(ai.t);
}
int main() {
f<int, 5>({1, 2, 3, 4, 5}); // OK
f<int, 5>({1, 2, 3, 4, 5, 6}); // "too many initializers for array<int, 5>"
std::initializer_list<int> il{1, 2, 3, 4, 5};
f<int, 5>(il); // ok
}
Note that both the non-static case at the top of the answer and the "head-desk" case do only check whether you provide too few initializing elements, and errors out then, for the initializer_list case. If you provide too many for the initializer_list case, the trailing elements are just ignored.
As far I know, no: you can't initialize a std::array with a std::initializer_list.
The problem is that std::array is intended as a lightweight replacement (a wrapper) for the classic C-style array. So light that is without constructors, so only implicit constructor can be used.
The construction with aggregate initialization (via implicit constructor) is possible because it's possible for the C-style array.
But std::initializer_list is a class, more complicated than an aggregate inizialization.
You can initialize, by example, a std::vector with a std::initializer_list but only because there is an explicit constructor, for std::vector, that receive a std::initializer_list. But std::vector is a heavier class.
The only solution that I see is a 2 step way: (1) construction and (2) copy of the std::initializer_list values. Something like
std::array<int, 3> arr5;
auto ui = 0U;
auto cit = il.cbegin();
while ( (ui < arr5.size()) && (cit != il.cend()) )
arr5[ui++] = *cit++;
p.s.: sorry for my bad English.
The problem with std::array is that it is required to be an aggregate type, hence it does not have constructors.
Hence only aggregate initialization or trivial copy are possible.
std::initializer_list is a class other than std::array, so a (missing) implicit conversion is required.
See http://en.cppreference.com/w/cpp/language/aggregate_initialization
and http://en.cppreference.com/w/cpp/container/array
for reference.

Ambiguous constructor call (I assume)

I have a matrix class defined like so:
template <typename T, unsigned int N, unsigned int M>
class TMatrixNxM //Rows x columns
{
public:
TMatrixNxM(T = T(0)); //Default constructor
TMatrixNxM(const std::array<std::array<T, M>, N>&); //Construct from array
TMatrixNxM(std::initializer_list<std::initializer_list<T>>); //Initializer
//...
private:
std::array<std::array<T, M>, N> data; //ROW-MAJOR
};
Now, in the code which uses matrices I have:
Math::Matrix3x3 b({{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
(Note: Matrix3x3 is a typedef for TMatrixNxM< float, 3, 3> and also, it is in a Math namespace)
Until now, it worked, because I didn't always have that array constructor, only initializer list one. But now, however, the compiler doesn't even finish compiling, it crashes! (I get "stopped working" popup and I have to close it, I am using MS VS Express 2013)
If I do it like this:
Math::Matrix3x3 b = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
Then it works fine. This is what I assume:
When I do it like that, then there is no ambiguity since you can only call the initializer_list constructor that way. With the first approach the compiler may be confused, since array is an aggregate type which means the initialization starts with double braces like: {{...}}, but also since I have an initializer list of initializer lists I have to use double braces. But should it really be a problem, since I never actually do double braces, those are single-braced lists inside a larger single-braces list?
What is really happening here and how do I solve this problem?
Thank you for your time!
EDIT
Compiler doesn't crash anymore if I make the constructor take the array by const pointer (since I'm never really going to straight-out plop the array in the constructor call, I have initializer list for that):
TMatrixNxM(const std::array<std::array<T, M>, N>*);
However can someone explain what was an actual problem before, was my assumption right?
Here's a minimal compilable (or, well, not) code that you can use to test:
#include <array>
#include <initializer_list>
template <typename T, unsigned int N, unsigned int M>
class TMatrixNxM //Rows x columns
{
public:
TMatrixNxM(T = T(0));
TMatrixNxM(const std::array<std::array<T, M>, N>&);
TMatrixNxM(std::initializer_list<std::initializer_list<T>>);
private:
std::array<std::array<T, M>, N> data; //ROW-MAJOR
};
template <typename T, unsigned int N, unsigned int M>
TMatrixNxM<T, N, M>::TMatrixNxM(T par_value)
{
std::array<T, M> temp;
temp.fill(par_value);
data.fill(temp);
}
template <typename T, unsigned int N, unsigned int M>
TMatrixNxM<T, N, M>::TMatrixNxM(const std::array<std::array<T, M>, N> &par_values)
{
data = par_values;
}
template <typename T, unsigned int N, unsigned int M>
TMatrixNxM<T, N, M>::TMatrixNxM(std::initializer_list<std::initializer_list<T>> par_values)
{
int i = 0;
for(std::initializer_list<T> row : par_values)
{
int j = 0;
for(T value : row)
{
data[i][j] = value;
++j;
}
++i;
}
}
int main()
{
TMatrixNxM<float, 3, 3> b({{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
return 0;
}
If you comment out the constructor declaration/definition with arrays then it should compile and execute fine. As stated previously, I am using compiler from MS VS Express 2013.
Well, looks like it just doesn't work on Microsoft's compiler. I tried to compile the same code on my Linux machine that uses GCC and it compiles with no problems. And it is indeed calling the initializer list constructor as it should. If I make an array in a variable and pass it, it also compiles fine, and it calls the array constructor:
std::array<float, 3> a = {{1, 2, 3}};
std::array<std::array<float, 3>, 3> b = {{a, a, a}};
TMatrixNxM<float, 3, 3> mat(b);
It also compiles and calls the array constructor if I initialize the array directly:
TMatrixNxM<float, 3, 3> mat({{ {{1, 2, 3}}, {{4, 5, 6}}, {{7, 8, 9}} }});

Can a class with this vector member be initialised using the implicit constructor?

My class needs a member std::vector<MyType> my_list with five elements. The MyTypes should be initialised with their default constructor. Will I have to write an explicit constructor for this class, or can it be solved without?
In C++11 you can do the following and get the vector properly initialized by the implicitly generated constructor:
class foo {
std::vector<int> my_list { 1, 2, 3, 4, 5 };
};
Without C++11, you have to write it yourself:
template <typename T, std::size_t N>
T* begin(T(&arr)[N]) { return &arr[0]; }
template <typename T, std::size_t N>
T* end(T(&arr)[N]) { return &arr[0] + N; }
class foo {
// imitate initializer list
static const int default_list[] = { 1, 2, 3, 4, 5 };
std::vector<int> my_list;
public:
foo() : my_list(begin(default_list), end(default_list)) {}
};
You'll need to write your own. The compiler generated constructor will default initialize my_list, which means it'll be empty. You need something like this:
class MyClass {
std::vector<MyType> my_list;
MyClass() : my_list(5) {}
};