Search code examples
c++c++14variadic-templatessfinaeinitializer-list

C++ SFINAE double nested initializer_list vs. variadic template constructor


Problem

I am writing a small math library that has Vector and Matrix classes. I'd like to make it convenient to intialize objects of these classes with strict rules for how they can be initialized.

The Vector class constructor already works as i intended. It is restricted such that only the correct size N is accepted and on top of that all arguments are of the same type (see below).

Vector

template <std::size_t N, typename T>
class Vector{
  public:
    template <typename... TArgs,
            std::enable_if_t<
                sizeof...(TArgs) == N &&
                    (std::is_same_v<T, std::remove_reference_t<TArgs>> && ...),int> = 0>
    Vector(TArgs &&... args) : data_{std::forward<TArgs>(args)...} {}

    // ...

  private:
    std::array<T,N> data;
}

Example initialization (works, is restricted)

Vector<3,int> v{1,2,3}

Matrix

template <std::size_t N, std::size_t M, typename T>
class Matrix{
  public:
    /* put restriction rules here */
    Matrix(std::initializer_list<std::initializer_list<T>> lst) {
      int i = 0;
      int j = 0;
      for (const auto &l : lst) {
        for (const auto &v : l) {
          data[i][j] = v;
          ++j;
        }
        j = 0;
        ++i;
      }
    }

    // ...

  private:
    std::array<Vector<M, T>, N> data;
}

Example initialization 1 (works, is not restricted)

Matrix<2,3,int> m{ {1,2,3}, {4,5,6} }

Example initialization 2 (compiles, but shouldn't!!)

Matrix<2,3,int> m{ {1,'a',3}, {4,5,6,7,8,9} }

Question

I wasn't able to implement the Matrix class constructor for the double-nested initialization with a variadic template, so i used nested std::initializer_list´s (from this post). It works, but i'd like to have the same restrictions on this constructor as for the Vector class.

How can i do this?

  • the list size must be N (rows)
  • the list-list size must always be M (columns)
  • the encapsulated value type must always be T

additional info

regarding this post here... ::std::initializer_list vs variadic templates i can say that i don't really care for using initializer_list or a variadic template. Either one seems fine in this case, but as far as i understand getting the size of an std::initializer_list at compile time is somewhat difficult.


Solution

  • To have you desired syntax, you might do:

    template <typename ... Us,
              std::enable_if_t<sizeof...(Us) == N && (std::is_same_v<Us, T> && ...), int> = 0>
    Matrix(const Us (&... rows)[M]) {
        // ...
    }