MaximV
MaximV

Reputation: 325

How do you pass fixed sized initializer lists of initializer lists through function parameters in C++?

I am making a matrix class where I want users to be able to instantiate the matrix like so:

Matrix<float, 2, 2> mat = { { 10, 20 }, { 30, 40 } };

My Matrix class is defined like so:

template<typename T, unsigned int ROWS, unsigned int COLS>
class Matrix
{
public:
    Matrix(std::array<std::array<T, ROWS>, COLS> matrix)
    {
        // ...
    }
    // ...
};

However, when I try instantiating the matrix, as I did above, I get "could not convert" errors from the compiler. I don't want to use initializer lists because I want compile-time errors to be triggered if the user defines the matrix with the wrong order. Does anyone why this isn't working? and if so is there an alternative?

Upvotes: 1

Views: 392

Answers (3)

Nelfeal
Nelfeal

Reputation: 13269

std::array only supports aggregate initialization. By using the Matrix<float, 2, 2> mat = { ... }; syntax, you are requesting copy initialization, which std::array just rejects. Note that, by taking a std::array<std::array<...>...> as constructor parameter, you allow the following initialization syntax: Matrix<float, 2, 2> mat{{ 10, 20, 30, 40 }};.

Most likely, what you want is a std::initializer_list parameter.

If you want your class to act like a std::array but with two dimensions, you might as well do what implementations of std::array do, which is having no constructor and making the inner member public:

template<typename T, unsigned int ROWS, unsigned int COLS>
class Matrix
{
public:
    T elements_[ROWS][COLS];
};

Here is the implementation of stdlibc++ (gcc): https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/std/array#L93. Note the declaration of _M_elems is public (array is a struct here). This means that you could access _M_elems directly if you know you are using this specific implementation of the standard library, but it's also the only way to get aggregate initialization working.

It also means you allow the syntax Matrix<float, 2, 2> mat2 = { 10, 20, 40, 40 };.

Demo

Upvotes: 2

Jake Schmidt
Jake Schmidt

Reputation: 1699

This seems to work...

template<typename T, unsigned int ROWS, unsigned int COLS>
class Matrix
{
public:
    Matrix(const std::array<T, ROWS> (&matrix)[COLS]) {
        // ... 
    }
};

int main() {
    Matrix<float, 2, 2> mat = {{ { 10, 20 }, { 40, 40 } }};
}

Though the error message is quite bad when it fails, and it only fails if you provide too many rows or columns!... for the same reason that std::array<int,3> a = {1,2}; is valid...

Edit:

Matrix(const T (&matrix)[COLS][ROWS]) {}

is also valid

Upvotes: 0

Jarod42
Jarod42

Reputation: 218098

Issue is that std::array uses aggregate initialization (so might have/require extra brace), but then you might have ambiguous call with copy/move constructor :-/

Following compiles:

template <std::size_t COLS, std::size_t ROWS>
struct Matrix
{
    Matrix(const std::array<std::array<float, COLS>, ROWS>&) {}
    //Matrix(const std::vector<std::vector<float>>&) {}
};

int main() {
    [[maybe_unused]]Matrix<3, 2> m1 = std::array{ std::array{1.f, 2.f, 3.f}, std::array{1.f, 2.f, 3.f}};
    [[maybe_unused]]Matrix<3, 2> m2 ({{ {{1.f, 2.f, 3.f}}, {{1.f, 2.f, 3.f}}}});
    //[[maybe_unused]]Matrix<3, 2> m3 ({ {1.f, 2.f, 3.f}, {1.f, 2.f, 3.f}}); // OK vector
}

Demo

Upvotes: 1

Related Questions