rxu rxu - 3 months ago 13
C++ Question

Specialize Many Templates for a Set of Types

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:

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];
}
}
/*
//The following doesn't work yet.
template <typename T>
void operator+=(const Scalar 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;
}

Answer

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.