Pass brace-enclosed initializer list of fixed length to function

895 views Asked by At

I want to make it possible to pass brace-enclosed initializer list to my constructor of my class which represents a matrix of fixed dimension. This is the code what I have:

#include <cstddef>
#include <array>

template <typename T, size_t N, size_t M>
using array2d = std::array<std::array<T, N>, M>;

template <typename T, size_t N, size_t M>
class Matrix {
    private:
        array2d<T, N, M> value;
    public:
        Matrix(const array2d<T, N, M>& value) : value(value) {}
};

int main() {
    Matrix<double, 2, 3> mat1 ({
        {1, 2, 3},
        {4, 5, 6}
    });
}

My assumption was that array2d is automatically created from initializer list that I'm passing as argument to constructor, but that doesn't seem to be the case, as the compiler throws the following error:

1704186652/source.cpp:16:23: error: no matching constructor for initialization of 'Matrix<double, 2, 3>'
        Matrix<double, 2, 3> mat1 ({
                             ^     ~
1704186652/source.cpp:8:7: note: candidate constructor (the implicit copy constructor) not viable: cannot convert initializer list argument to 'const Matrix<double, 2, 3>'
class Matrix {
      ^
1704186652/source.cpp:8:7: note: candidate constructor (the implicit move constructor) not viable: cannot convert initializer list argument to 'Matrix<double, 2, 3>'
class Matrix {
      ^
1704186652/source.cpp:12:3: note: candidate constructor not viable: cannot convert initializer list argument to 'const array2d<double, 2UL, 3UL>' (aka 'const std::__1::array<std::__1::array<double, 2>, 3>')
                Matrix(const array2d<T, N, M>& value) : value(value) {}
                ^
2

There are 2 answers

2
Sam Varshavchik On BEST ANSWER

For starters, you have your dimensions reversed.

Matrix<double, 2, 3>

If you expand your template, this becomes

std::array<std::array<double, 2>, 3>

Or a three element array with each element being two values, and not a two element array with three values per element.

Braced initialization lists, with nested constructors and parameters, sometimes can be a challenge to decipher, and quite often you just have to poke and scribble randomly until it compiles. The following compiles with gcc 10.2, and checked with a debugger that everything is where it should be. My best attempt at explaining the braces:

#include <cstddef>
#include <array>

template <typename T, size_t N, size_t M>
using array2d = std::array<std::array<T, N>, M>;

template <typename T, size_t N, size_t M>
class Matrix {
    private:
        array2d<T, N, M> value;
    public:
        Matrix(const array2d<T, N, M>& value) : value(value) {}
};

int main() {
    Matrix<double, 2, 3> mat1({ // Initializing the 1st constructor parameter
            { // std::array<   ,3> using initializer_list constructor

                // Initializing std::array<T, 2>s
                {1, 2},
                {3, 4},
                {5, 6}
            },
        });

    return 0;
}
0
StefanKssmr On

Personally I would recommend to use @SamVarshavchik's solution. But if you want to save a pair of {}, you can do the following:

#include <iostream>
#include <cstddef>
#include <array>

template <typename T, size_t N, size_t M>
using array2d = std::array<std::array<T, N>, M>;

template <typename T, size_t N, size_t M>
class Matrix {
    private:
        array2d<T, N, M> value;
    public:
        Matrix(const array2d<T, N, M>& value) : value(value) {
            std::cout << "using array2d" << std::endl;}
        Matrix(std::initializer_list<std::array<double,N>> values)   {
            // Assign values to this->value
            std::cout << "using initializer list" << std::endl;
        }
};

int main() {

    array2d<double, 2, 3> values;
    Matrix<double, 2, 3> mat0(values);
    Matrix<double, 2, 3> mat1({{1, 2},{3, 4},{5, 6}});
    Matrix<double, 2, 3> mat2({{1, 2},{3, 4},{5, 6},{7,8}}); // > 3 rows but compiles
    //Matrix<double, 2, 4> mat3({{{1, 2},{3, 4},{5, 6}}}); // <- ambiguous. Note the extra "{}"

    return 0;
}

This prints

using array2d
using initializer list

Assigning the values of the list to your this->value is left as a homework.

Note that you can add more rows to the initializer list than there are rows in your matrix. When using this solution, you should provide a runtime check. For this reason I prefer @SamVarshavchik's solution, there the compiler tells you what's wrong.