I am a newbie in C++ and just learned how to use templates. I want to write a Matrix class with the following template arguments:
template < int nRow ,int nCol ,typename T = double >
class ZDMatrix {
public:
ZDMatrix();
ZDMatrix( T arg0 );
ZDMatrix( T arg0 ,T arg1 );
// ... How to declare the constructor with exactly nRow*nCol arguments of type T ??
// ZDMatrix( T arg0 ,T arg1 ,... ) {
// ... number or argN should be nRow*nCol
// }
};
I want to be able to use the matrix like this:
ZDMatrix<2,3> matX( 1 ,1.1 ,1.2 ,2,2.1,2.2 ); // auto convert int 1 to double 1.0
As an alternative, I already try using std::initializer_list
:
ZDMatrix<nRow,nCol,double>::ZDMatrix( std::initializer_list<T> list ) {
assert( list.size() == nRow*nCol );
// ...
}
But the check for the number of arguments is at run time not static_assert()
.
How do you declare the constructor with exactly
nRow*nCol
arguments of typeT
??
One way is by using sizeof...
, the number of elements in a parameter pack can be checked, assuming you use the constructor with variadic template arguments. Applying SFINAE, it would look like:
#include <type_traits> // std::conjunction (since c++17), std::enable_if, std::is_same
template <std::size_t nRow, std::size_t nCol, typename T = double>
class ZDMatrix
{
std::array<T, nRow* nCol> mData{};
public:
template <typename... Ts,
std::enable_if_t<sizeof...(Ts) == nRow * nCol && // No. of args must be equal tonRow * nCol
std::conjunction_v<std::is_same<T, std::remove_cvref_t<Ts>>...> // or std::is_convertible, std::decay_t<Ts>
>* = nullptr>
ZDMatrix(Ts&&... args)
: mData{ std::forward<Ts>(args)... }
{}
};
Alternatively, you could static_assert
the above two SFINAE conditions inside the constructor body. Or in C++20 you would do requires
constraints for the same conditions as shown below:
template <std::size_t nRow, std::size_t nCol, typename T = double>
class ZDMatrix
{
std::array<T, nRow* nCol> mData{};
public:
template <typename... Ts>
requires (sizeof...(Ts) == nRow * nCol) && (std::same_as<T, std::remove_cvref_t<Ts>> && ...)
ZDMatrix(Ts&&... args)
: mData{ std::forward<Ts>(args)... }
{}
};
As a side note, the compiler will always tell the type mismatch while using the templated constructor. I would choose a template solution unless there is a special requirement to have std::initializer_list<T>
and assert
macro.