Search code examples
c++classc++11templatesinitializer-list

How would I get this functionality in my matrix class?


#include <iostream>
#include <array>

template<typename T, std::size_t R, std::size_t C>
class matrix
{
   std::array<T, R * C> m_data;
};

int main() 
{
   matrix<float, 2, 2> a = { 1,2,3,4 }; // COMPILER ERROR!
}

Clang reports that there is no matching constructor!

I have tried writing a constructor of the form

matrix(std::array<T,R*C> a);

and tried experimenting with && as I think the right-hand side of the expression in question is temporary. Which leads me to some confusion. As we would expect that it would be created then assigned(!) to the value of a.


Solution

  • Like others mentioned in the comments, you need a std::initializer_list<T> constructor for your matrix class.

    #include <array>            // std::array
    #include <initializer_list> // std::initializer_list
    #include <algorithm>        // std::copy
    #include <cassert>
    
    template<typename T, std::size_t R, std::size_t C>
    class matrix
    {
       std::array<T, R * C> m_data;
    public:
       matrix(const std::initializer_list<T> list)
      //            ^^^^^^^^^^^^^^^^^^^^^^^ --> constructor which takes std::initializer_list
       {
          assert(R * C == list.size());
          std::copy(list.begin(), list.end(), m_data.begin());
       }
    };
    
    int main()
    {
       matrix<float, 2, 2> a = { 1,2,3,4 }; // now compiles
    }
    

    (See live online)


    However, that is not compile-time and come ups with the following drawbacks:

    • It does not check the passed types are same.
    • Size of the passed list can not be checked compile-time, as std::initializer_list is/can not be compile time.
    • Thirdly, it allows narrowing conversion.

    In order to disallow the above, one solution is to provide a variadic template constructor which enables the above checks compile-time.

    Something like as follows:(See live online)

    #include <array>            // std::array
    #include <initializer_list> // std::initializer_list
    #include <type_traits>      // std::conjunction, std::is_same
    #include <utility>          // std::forward     
    
    // traits for checking the types (requires C++17)
    template <typename T, typename ...Ts>
    using are_same_types = std::conjunction<std::is_same<T, Ts>...>;
    
    template<typename T, std::size_t R, std::size_t C>
    class matrix
    {
       std::array<T, R * C> m_data;
    public:
       template<typename... Ts>
       constexpr matrix(Ts&&... elemets) noexcept
       {
          static_assert(are_same_types<Ts...>::value, "types are not same!");
          static_assert(sizeof...(Ts) == R*C, "size of the array does not match!");
          m_data = std::array<T, R * C>{std::forward<Ts>(elemets)...};
       }
    };
    
    int main()
    {
       matrix<float, 2, 2> a{ 1.f,2.f,3.f,4.f }; // now compiles
       // matrix<float, 2, 2> a1{ 1,2,3,4 };              // error: narrowing conversion!
       // matrix<float, 2, 2> a1{ 1.f,2.f,3.f, 4 };       // error: types are not same!
       // matrix<float, 2, 2> a1{ 1.f,2.f,3.f,4.f, 5.f }; // error: size of the array does not match!
    }