Specialize Many Templates for a Set of Types

2019-04-15 07:13发布

问题:

How to specialize many template for all kinds of scalar values? (such as int, float, size_t, uint32_t, and types defined in the stdint header)?

Can I avoid specializing each template for each of the types? I don't want to use boost or other non-standard libraries if possible.

There are some solutions at template specialization for a set of types:

  1. Replace each template with multiple functions. One function for each scalar type. (But there are many templates. That would mean writing many functions.)

  2. Fail if the template takes a non-scalar type. (But I also want to write template for array types. This would mean I need to change the names of the functions. One set of function names for scalar-scalar calculation. Another set for scalar-matrix calculation. Yet another set for matrix-matrix calculation. If I am trying to overload operators, i guess this won't work.)

  3. Metaprogramming solution by Nawaz. Same problem as in solution 2 for this case.

  4. Specialize a generic template for each scalar type. For example, write inline long getRatio<long>, inline long getRatio<float>, etc. Can work, but need to do that for the many templates.

Thanks again.

Example (this uses Andrew's solution. adapted for an old std library. still need c++11. compiled with intel icc -std=c++11):

#define STD_POORMAN stdpoor
namespace stdpoor{
    template<bool B, class T = void>
    struct enable_if_t {}; 
    template<class T>
    struct enable_if_t<true, T> { typedef T type; };

    template<class T, T v>
    struct integral_constant {
        static constexpr T value = v;
        typedef T value_type;
        typedef integral_constant type;
        constexpr operator value_type() const {
            noexcept return value;
        }
        constexpr value_type operator()() const {
            noexcept return value;
        }
    };

    typedef integral_constant<bool,true> true_type;
    typedef integral_constant<bool,false> false_type;
}
template <typename T>
class SimpleArray;

template <typename T>
struct is_ndscalar : STD_POORMAN::false_type {};
// Specialisations for supported scalar types:
template <> struct is_ndscalar<int> : STD_POORMAN::true_type {};
template <> struct is_ndscalar<float> : STD_POORMAN::true_type {};
template <> struct is_ndscalar<double> : STD_POORMAN::true_type {};
template <> struct is_ndscalar<long> : STD_POORMAN::true_type {};
template <> struct is_ndscalar<long long> : STD_POORMAN::true_type {};



template <typename T>
class SimpleArray{
    public:
        T* ar_data; //pointer to data
        int size; //#elements in the array
        SimpleArray(T* in_ar_data, int in_size){
            ar_data = in_ar_data;
            size = in_size;
        };

        template <typename T>
        void operator+=(const SimpleArray<T>& B){
            //array-array +=
            int i;
            for(i = 0; i < size; ++i){
                ar_data[i] += B.ar_data[i];
            }
        }

        template <typename T>
        STD_POORMAN::enable_if_t<is_ndscalar<T>::value, void>
        operator+=(const T b){
            //array-scalar +=
            int i;
            for(i = 0; i < size; ++i){
                ar_data[i] += b;
            }
        }
};

int main(void){
    int base_array[10];
    SimpleArray<int> A(base_array, 10);
    A += A;
    A += 3; 
}

回答1:

Condensing this down to a smaller example, based on the discussion in the comments on the question, you have a type Matrix<T> and you wish to implement, say, operator+=. The behaviour of this operator differs depending on whether the operand is a scalar or another matrix.

You therefore want to provide two specialisations; one for matrix-scalar operations, and one for matrix-matrix operations. Within those, you want to accept any valid scalar type, or any valid matrix type.

This is a classic use case for type traits and SFINAE using std::enable_if. Define a trait is_scalar:

// Base template:
template <typename T>
struct is_scalar : std::false_type {};

// Specialisations for supported scalar types:
template <> struct is_scalar<int> : std::true_type {};
template <> struct is_scalar<float> : std::true_type {};
template <> struct is_scalar<double> : std::true_type {};
// etc.

And a trait is_matrix:

// Base template:
template <typename T>
struct is_matrix : std::false_type {};

// Specialisations:
template<typename T>
struct is_matrix<Matrix<T>> : std::true_type {};
// and possibly others...

Your operators will then be (member) function templates of the form:

template <typename T>
std::enable_if_t<is_scalar<T>::value, Matrix&> operator+=(const T& rhs) {
  // Implementation for addition of scalar to matrix
}

template <typename T>
std::enable_if_t<is_matrix<T>::value, Matrix&> operator+=(const T& rhs) {
  // Implementation for addition of matrix to matrix
}

Note that is_scalar is already provided for you by the standard library! All this leaves is for you to define is_matrix specialisations for any matrix types you support.



回答2:

If you're trying to implement this template only for certain types you can declare them in a .cpp file, similar to this: Why can templates only be implemented in the header file?

If you want to allow anything to be in this template, but explicitly declare certain types, this link might be helpful: http://en.cppreference.com/w/cpp/language/template_specialization