1

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.

mtosch
  • 351
  • 4
  • 18

2 Answers2

2

You can simple make Matrix take a 2D array reference as such:

Matrix(T const (&m)[N][M])

This will only compile under the conditions you specify and adapting the rest of your code is also easy. You can apply the same principle to simplify Vector.

Peter
  • 2,919
  • 1
  • 16
  • 35
  • with this solution i would need an extra pair of braces when initializing: `Matrix<2,3,int> m{ { {1,2,3}, {4,5,6} } }` and also something like the 'a' is not prevented. (I suppose the latter can be corrected with a template restriction on T.) If possible, i'd prefer not having to write out an extra pair of braces – mtosch Dec 15 '20 at 14:03
  • @mtosch: I can see the braces thing, but why would you even want to prevent passing e.g. characters? That seems too restrictive to me, e.g. if i want to brace-initialize a `std::vector` I can also pass anything that is implicitly convertible to `int`. – Peter Dec 15 '20 at 14:12
  • If you want to get rid of the braces, I suppose you could use the variadic template approach in combination with array references. – Peter Dec 15 '20 at 14:15
  • what's the syntax for combining variadic template ... with array references? I can't seem to get it right. I can do it with an initializer list easily: `template Matrix(std::initializer_list &&... args)` – mtosch Dec 15 '20 at 14:35
  • Ok, just figured it out: `templateMatrix(TArgs const (&... args)[M])` It is mentioned in https://en.cppreference.com/w/cpp/language/parameter_pack under "Function parameter list" – mtosch Dec 15 '20 at 15:14
1

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]) {
    // ...
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302