2

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; 
}
Community
  • 1
  • 1
rxu
  • 1,369
  • 1
  • 11
  • 29
  • 1
    What's your specific problem you're trying to solve? This is enormously broad. – Barry Sep 09 '16 at 15:58
  • I second Barry's request for a narrower problem statement. It's not clear whether you want to simply permit a number of templates to be used for anything you determine to be a "scalar type", for which SFINAE and an appropriate trait suffices, or whether you need those specialisations to contain different code for `int` compared to `float` (for example). A concrete example would likely result in more targeted advice. – Andrew Sep 09 '16 at 16:00
  • writing a small library for array operation, similar to the numpy library of python. At this moment, I want to specialize each template for a calculation (such as operators *=, += etc) for scalar type, and also able to specialize the templates for all matrix types (matrix of int, matrix of float etc). – rxu Sep 09 '16 at 16:01
  • I see. let me add an example then. – rxu Sep 09 '16 at 16:01
  • Do you mean "specialise", or "enable"? Specialising means that you expect a different implementation for `float` compared to `int`, for e.g. `operator+=`. Enabling simply means you allow `float` or `int` (or any other identified "scalar type") instantiations, but not `Matrix` or `std::string` (or anything not identified as a "scalar type") instantiations. It's sounding like this is what you want, but hopefully the example will add further clarity. – Andrew Sep 09 '16 at 16:03
  • i want specialize, not enable. The code for scalar-array calculation is not the same as the code for array-array calculation. – rxu Sep 09 '16 at 16:05
  • @rxu Indeed it is not. But you don't want to write a specialisation for `int`, another specialisation for `float`, and yet another specialisation for `double` - all of which are scalar-matrix calculations. You similarly don't want to write a specialisation for `Matrix`, another for `Matrix`, etc. What you want is to select a specialisation based on a *trait*, such as `is_scalar` or `is_matrix`. Your specialisations are then only for "matrix-scalar" ops and "matrix-matrix" ops. – Andrew Sep 09 '16 at 16:07
  • @Andrew, you are right. That is what I want. I have also made an example as quickly as I can. – rxu Sep 09 '16 at 16:14

2 Answers2

4

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.

Andrew
  • 5,212
  • 1
  • 22
  • 40
0

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

hdt80
  • 639
  • 1
  • 8
  • 14