I wanted to create an array:
template < typename T, typename ... A > struct a {
T x [1 + sizeof... (A)];
a () = default;
a (T && t, A && ... y) : x { t, y... } {}
};
int main () {
a < int, int > p { 1, 1 }; // ok
a < a < int, int >, a < int, int > > q { { 1, 1 }, { 3, 3 } }; // error: bad array initializer
}
Why doesn't it compile? (tested with g++ 4.6)
I'm pretty sure that's a bug. {} can be used in place of () for supplying the constructor with arguments. Therefore your code should be ok:
int main ()
{
// this is fine, calls constructor with {1, 1}, {3, 3}
a<a<int, int>, a<int, int>> q({ 1, 1 }, { 3, 3 });
// which in turn needs to construct a T from {1, 1},
// which is fine because that's the same as:
a<int, int>{1, 1}; // same as: a<int, int>(1, 1);
// and that's trivially okay (and tested in your code)
// we do likewise with A..., which is the same as above
// except with {3, 3}; and the values don't affect it
}
So the whole thing should be okay.
Related
I'd like a template function that returns some initialisation.
I did the following:
#include<iostream>
#include<array>
template <typename T>
inline static constexpr T SetValues()
{
if(std::is_integral<T>::value)
{
std::cout<<"int\n";
return T{1};
}
else
{
std::cout<<"array\n";
T arr = {1,2,3,4};
return arr;
}
}
int main()
{
int a = 6;
std::array<int, 4> arr = {2,2,2,2};
a = SetValues<decltype(a)>();
arr = SetValues<decltype(arr)>();
return 0;
}
which correctly initialises the int but in case of the array I get the error
scalar object ‘arr’ requires one element in initializer
How should I initialise and return the array?
The problem is that, when T = int, you're trying to trying to initialize int arr with initializer list {1, 2, 3, 4} in the else branch. Even if you don't execute the else branch, the compiler still has to compile it and int arr = {1, 2, 3, 4} is invalid syntax.
On C++17 (and higher), you can get away with if constexpr:
template <typename T>
inline static constexpr T SetValues() {
if constexpr (std::is_integral<T>::value) {
std::cout << "int\n";
return T{1};
} else {
std::cout << "array\n";
T arr = {1, 2, 3, 4};
return arr;
}
}
With if constexpr, the compiler won't compile the else branch when T is an integral type (e.g. int) and will not compile the if branch when T is not an integral type.
This is the code I found online.
template<class T, unsigned ... RestD> struct array;
template<class T, unsigned PrimaryD >
struct array<T, PrimaryD>
{
typedef T type[PrimaryD];
type data;
T& operator[](unsigned i) { return data[i]; }
};
template<class T, unsigned PrimaryD, unsigned ... RestD >
struct array<T, PrimaryD, RestD...>
{
typedef typename array<T, RestD...>::type OneDimensionDownArrayT;
typedef OneDimensionDownArrayT type[PrimaryD];
type data;
OneDimensionDownArrayT& operator[](unsigned i) { return data[i]; }
};
int main()
{
array<int, 2, 3>::type a4 = { { 1, 2, 3}, { 1, 2, 3} };
array<int, 2, 3> a5{ { { 1, 2, 3}, { 4, 5, 6} } };
std::cout << a5[1][2] << std::endl;
array<int, 3> a6{ {1, 2, 3} };
std::cout << a6[1] << std::endl;
array<int, 1, 2, 3> a7{ { { { 1, 2, 3}, { 4, 5, 6 } } }};
std::cout << a7[0][1][2] << std::endl;
}
Could you explain what this code does exactly? I understand that recursion is used in some form here to create a multidimensional array, but I am a little confused on how this process works.
I am also confused about this line:
array<int, 2, 3>::type a4 = { { 1, 2, 3}, { 1, 2, 3} };
What is the ::type?
This is whats called template specialization.
The first thing you must know is that anything defined with template is evaluated at compile time.
Looking at your code, there are two main cases we want to consider:
When the array is of one-dimension
When the array has more than one dimension
The following describes the one-dimensional case:
template<class T, unsigned PrimaryD >
struct array<T, PrimaryD>
{
typedef T type[PrimaryD];
type data;
T& operator[](unsigned i) { return data[i]; }
};
As you can see the above struct is specialized to only accept two template parameters, the first one is T the data type of the array, and the second is PrimaryD which simply means how long should the array be.
Now for the 2nd specialization:
template<class T, unsigned PrimaryD, unsigned ... RestD >
struct array<T, PrimaryD, RestD...>
{
// if the template contains array<int, 1, 2, 3, 4>
// then RestD... will unpack the values 2, 3, 4
typedef typename array<T, RestD...>::type
OneDimensionDownArrayT;
typedef OneDimensionDownArrayT type[PrimaryD];
type data;
// This will return an array type
// you can keep on making a call to operator[] until the type of the OneDimensionDownArrayT refers to the previous specialization
OneDimensionDownArrayT& operator[](unsigned i) { return
data[i];
}
};
Now for your example:
array<int, 2, 3>::type a4 = { { 1, 2, 3}, { 1, 2, 3} }
What the compiler will do is sort of match the template parameters namely, int, 2, and 3 which evaluate to the 2nd specialization.
Let's follow it through:
array<int, 2, 3> which is a struct contains the following concrete types:
OneDimensionalT which is equal to array<int, 3>
and an operator[] function that returns an array<int, 3>.
OneDimensionalT is equal to array<int, 3> because if you unpack the RestD... values you will only get 3, the 2 is already consumed by the first template parameter, PrimaryD.
Adding a 3rd dimension follows the same logic, keep on defining smaller dimension struct array types recursively using only the n-1 dimension values of the parameter pack RestD...
Oh and btw, the line on the top is just to declare the base type so we can specialize it.
I'm using a template library and since in my code one of the template parameters can assume a finite range of values, I decided to use, under suggestion, std::variant and declare in it all the objects I will possibly need:
std::variant<TemplateClass<1>, TemplateClass<2>, ..., TemplateClass<5>>
I never used this utility.
To access the methods of TemplateClass I have to use std::visit, but sometimes it works and other it doesn't, say no member function XXX in std::variant < .... > or "In instantiation of function template specialization ... " [I don't even understand what is the problem here]
Specifically, I'm using Eigen::Tensor library and when I call methods like rank(), dimension(n), it works, while for methods like dimensions() and setRandom() it doesn't.
Below a draft of my implementation
std::variant<Eigen::Tensor<double, 1>, Eigen::Tensor<double, 2>, /* ... */> makeTensor(
int i, const std::initializer_list<int> dims) {
switch (i) {
case 1: {
Eigen::Tensor<double, 1> T1;
T1.resize(dims);
return T1;
}
case 2: {
Eigen::Tensor<double, 2> T2;
T2.resize(dims);
return T2;
}
/* ... */
}
}
int main() {
auto myTensor{makeTensor(2, {4, 5})}; // Tensor 2D 4x5
// Working methods
auto rnk = std::visit([](const auto &tensor) { return tensor.rank(); }, myTensor);
auto dim1 = std::visit([](const auto &tensor) { return tensor.dimension(0); }, myTensor);
// Not working methods
auto dimsTens =
std::visit([](const auto &tensor) { return tensor.dimensions(); }, myTensor); // 5 times same error saying
//'In instantiation of function template specialization 'std::visit<(lambda at
/// home/virginie/Desktop/Project/main.cpp:62:33),
// std::variant<Eigen::Tensor<double, 1, 0, long>, Eigen::Tensor<double, 2, 0, long>, Eigen::Tensor<double, 3, 0,
// long>, Eigen::Tensor<double, 4, 0, long>, Eigen::Tensor<double, 5, 0, long>> &>''
std::visit([&myTensor]() { myTensor.setRandom(); }); // 'No member setRandom() in std::variant<...>'
}
Am I using std::visit in the wrong way?
---- EDIT ----
After the suggestion of #florestan, I have solved the problem related to dimensions(), while with setRandom I get the following:
In file included from /..../ main.cpp
required from here
You need to return the same type for all possible alternatives. In case of dimension, you need to copy the array elements to a vector, for example.
Something like this should help:
auto dimsTens=std::visit(
[](const auto &tensor) {
auto dims = tensor.dimensions();
return std::vector<int>(dims.begin(), dims.end());
}, myTensor);
The second error is because you don't call std::visit the right way. It needs two params, first the function and second the variant to visit. The following should work.
std::visit([](auto& t){ t.setRandom();}, myTensor);
Live code:
https://godbolt.org/z/vq4PYo
For example, I want to generate a reverse ordered array, but the codes are really redundant.
The constructor is on run-time but the array size is on compile time. However, these codes are very similar.
#include <array>
#include <cstdint>
using std::array;
array<int32_t, 5> genReverse5() {
array<int32_t, 5> a{5, 4, 3, 2, 1};
return a;
}
array<int32_t, 4> genReverse4() {
array<int32_t, 4> a{ 4, 3, 2, 1};
return a;
}
array<int32_t, 3> genReverse3() {
array<int32_t, 3> a{ 3, 2, 1};
return a;
}
You can try with std::integer_sequence (since C++14).
template <std::size_t... I>
auto genReverse_impl(std::index_sequence<I...>) {
array<int32_t, sizeof...(I)> a{ (sizeof...(I) - I)... }; // decrease progressively
return a;
}
template<std::size_t N, typename Indices = std::make_index_sequence<N>>
auto genReverse()
{
return genReverse_impl(Indices{});
}
then use it as
genReverse<5>(); // returns array initialized as {5, 4, 3, 2, 1}
genReverse<4>(); // returns array initialized as {4, 3, 2, 1}
genReverse<3>(); // returns array initialized as {3, 2, 1}
LIVE
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}} }});