4

I am making a templated Matrix class, and I have limited the template parameters to the integral and floating point data types by

template class Matrix<int>;
template class Matrix<float>; ..etc

I was implementing a random() static member function, and to make it uniform random distribution from 0.0 to 1.0, I used the std::is_floating_point<T> to limit the use of templates of floating point types. and I thought that the static_assert will fire if only T is not a floating point type, but the assertion fails for every template class Matrix<T>; where T is an integral type.

When I comment out all integral types, it works fine, but I can't do this as I need to be able to make Matrix<T> instances with T is an integral type. How would I fix it?

Note that I have provided the template class Matrix<T> for every integral/floating-point type because I get the undefined reference error. So I limit the initialization to the integral and floating point types.

// Matrix.cpp
template<typename T>
Matrix<T> Matrix<T>::rand(const size_t& r, const size_t& c) {
    Matrix<T> result{ r, c };
    static_assert(std::is_floating_point<T>::value,
        "result_type must be a floating point type");
    const float range_from = 0.0;
    const float range_to = 1.0;
    std::random_device                  rand_dev;
    std::mt19937                        generator(rand_dev());
    std::uniform_real_distribution<T>   distr(range_from, range_to);
    for (int i = 0; i < r; i++) {
        for (int j = 0; j < c; j++) {
            result[i][j] = distr(generator);
        }
    }
    return result;
}
//...
template class Matrix<int>;
template class Matrix<long>;
template class Matrix<float>;
template class Matrix<double>;
Tortellini Teusday
  • 1,335
  • 1
  • 12
  • 21
  • 3
    You need SFINAE (or `requires` of C++20) or specialization if you explicitly instantiate the class (and so all its (non template) methods) – Jarod42 Jun 14 '19 at 23:42

2 Answers2

3

You have several choices:

  • provide (template) code in header instead of cpp (see why-can-templates-only-be-implemented-in-the-header-file)
  • explicitly instantiate methods instead of class:

    // all individual available methods of the class
    template Matrix<int> Matrix<int>::other_method(/*..*/); 
    // ...
    
    // Whole class
    template class Matrix<float>;
    template class Matrix<double>;
    
  • Use SFINAE:

    template<typename T>
    class Matrix
    {
        template <typename U = T,
                  std::enable_if_t<std::is_same<T, U>::value
                                   && std::is_floating_point<U>::value, int> = 0>
        Matrix<T> rand(const size_t &r, const size_t &c);
    // ...
    };
    
  • or requires from C++20:

    template<typename T>
    class Matrix
    {
        Matrix<T> rand(const size_t &r, const size_t &c) requires (std::is_floating_point<T>::value);
    // ...
    };
    
  • or specialize the class

    template<typename T, typename Enabler = void>
    class Matrix
    {
    // ...
    };
    
    template<typename T>
    class Matrix<T, std::enable_if_t<std::is_floating_point<T>::value>>
    {
        Matrix<T> rand(const size_t &r, const size_t &c);
    // ...
    };
    
Jarod42
  • 203,559
  • 14
  • 181
  • 302
2

When I comment out all integral types, it works fine, but I can't do this as I need to be able to make Matrix<T> instances with T is an integral type. How would I fix it?

As @Jarod42 pointed out in the comments, you could apply the SFINAE to restrict the use of rand() function if and only if the template type is a floating point.

Bonus remark: The same technique can be applied to restrict the instantiation of the class Matrix<T>, by instantiating the class conditionally for the allowed types mentioned in the traits.

Following is an example code(compiles with ) which demonstrates the idea.

(See online)

#include <iostream>
#include <type_traits> // for std::conjunction, std::negation, std::is_arithmetic, std::is_floating_point, std::enable_if

// traits for filtering out the allowed types
template<typename Type>
using is_allowed = std::conjunction<
    std::is_arithmetic<Type>,    // is equal to  std::is_integral_v<T> || std::is_floating_point_v<T>>
    std::negation<std::is_same<Type, bool>>,    // negate the types which shouldn't be compiled
    std::negation<std::is_same<Type, char>>,
    std::negation<std::is_same<Type, char16_t>>,
    std::negation<std::is_same<Type, char32_t>>,
    std::negation<std::is_same<Type, wchar_t>>
>;

template<typename Type, typename ReType = void>
using is_allowed_type = std::enable_if_t<is_allowed<Type>::value, ReType>;

template<typename Type, typename Enable = void> class Matrix;
// conditional instantiation of the template class
template<typename Type> class Matrix<Type, is_allowed_type<Type>> /* final */
{
public:
    template<typename T = Type>
    std::enable_if_t<std::is_floating_point_v<T>, Matrix<T>> rand(const size_t& r, const size_t& c)
   //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SFINAE to restrict the use of rand()
    {
        Matrix<T> result{/*args*/};
        // other code
        return result;
    }
};
template class Matrix<int>;
template class Matrix<long>;
template class Matrix<float>;

int main()
{
   Matrix<double> obj;
   obj.rand(1, 2);
}
JeJo
  • 30,635
  • 6
  • 49
  • 88