Lennie De Villiers
Lennie De Villiers

Reputation: 345

Restrict Template Function

I wrote a sample program at http://codepad.org/ko8vVCDF that uses a template function.

How do I retrict the template function to only use numbers? (int, double etc.)

#include <vector>
#include <iostream>

using namespace std;

    template <typename T>
T sum(vector<T>& a)
{
    T result = 0;
    int size = a.size();
    for(int i = 0; i < size; i++)
    {
        result += a[i];
    }

    return result;
}

int main()
{
    vector<int> int_values;
    int_values.push_back(2);
    int_values.push_back(3);
    cout << "Integer: " << sum(int_values) << endl;

    vector<double> double_values;
    double_values.push_back(1.5);
    double_values.push_back(2.1);
    cout << "Double: " << sum(double_values);

    return 0;
}

Upvotes: 16

Views: 25871

Answers (8)

Cipher
Cipher

Reputation: 41

Suppose we want our templated add function can only accepts int and floats, We can do something like below. Can be seen here: https://godbolt.org/z/qa4z968hP

#include <fmt/format.h>

template <typename T> struct restrict_type {};
template<> struct restrict_type<float> {typedef float type;};
template<> struct restrict_type<int> {typedef int type;};

template<typename T>
typename restrict_type<T>::type add(T val1, T val2){
    return val1 + val2;
}

int main()
{
    fmt::print("{}\n", add(12, 30));
    fmt::print("{}\n", add(12.5f, 30.9f));
}

Upvotes: 0

Leon Timmermans
Leon Timmermans

Reputation: 30225

This is possible by using SFINAE, and made easier by using helpers from either Boost or C++11

Boost:

#include <vector>
#include <boost/utility/enable_if.hpp>
#include <boost/type_traits/is_arithmetic.hpp>

template<typename T> 
    typename boost::enable_if<typename boost::is_arithmetic<T>::type, T>::type 
        sum(const std::vector<T>& vec)
{
  typedef typename std::vector<T>::size_type size_type;
  T result;
  size_type size = vec.size();
  for(size_type i = 0; i < size; i++)
  {
    result += vec[i];
  }

  return result;
}

C++11:

#include <vector>
#include <type_traits>

template<typename T> 
    typename std::enable_if<std::is_arithmetic<T>::value, T>::type 
        sum(const std::vector<T>& vec)
{
  T result;
  for (auto item : vec)
    result += item;
  return result;
}

Upvotes: 21

Lou Franco
Lou Franco

Reputation: 89162

The only way to restrict a template is to make it so that it uses something from the types that you want, that other types don't have.

So, you construct with an int, use + and +=, call a copy constructor, etc.

Any type that has all of these will work with your function -- so, if I create a new type that has these features, your function will work on it -- which is great, isn't it?

If you want to restrict it more, use more functions that only are defined for the type you want.

Another way to implement this is by creating a traits template -- something like this

template<class T>
SumTraits
{
public:
  const static bool canUseSum = false;
}

And then specialize it for the classes you want to be ok:

template<>
class SumTraits<int>
{
  public:
    const static bool canUseSum = true;
};

Then in your code, you can write

if (!SumTraits<T>::canUseSum) {
   // throw something here
}

edit: as mentioned in the comments, you can use BOOST_STATIC_ASSERT to make it a compile-time check instead of a run-time one

Upvotes: 19

Jeff Hillman
Jeff Hillman

Reputation: 7598

You can do something like this:

template <class T>
class NumbersOnly
{
private:
    void ValidateType( int    &i ) const {}
    void ValidateType( long   &l ) const {}
    void ValidateType( double &d ) const {}
    void ValidateType( float  &f ) const {}

public:
    NumbersOnly()
    {
       T valid;
       ValidateType( valid );
    };
};

You will get an error if you try to create a NumbersOnly that doesn't have a ValidateType overload:

NumbersOnly<int> justFine;
NumbersOnly<SomeClass> noDeal;

Upvotes: 19

user23415
user23415

Reputation:

That is how you do it.

Comment the template specialization for double for example.. and it will not allow you to call that function with double as parameter. The trick is that if you try to call sum with a type that is not among the specializations of IsNumber, then the generic implementation is called, and that implementation makes something not allowed (call a private constructor).

The error message is NOT intuitive unless you rename the IsNumber class to something that sounds like an error message.

#include <vector>
#include <iostream>

using namespace std;

template<class T> struct IsNumber{ 
 private:
 IsNumber(){}
 };

 template<> struct IsNumber<float>{
   IsNumber(){};
 };

 template<> struct IsNumber<double>{
   IsNumber(){};
 };

 template<> struct IsNumber<int>{
   IsNumber(){};
 };

template <typename T>
T sum(vector<T>& a)
{
 IsNumber<T> test;
 T result = 0;
 int size = a.size();
 for(int i = 0; i < size; i++)
 {
  result += a[i];
 }

 return result;
}




int main()
{
 vector<int> int_values;
 int_values.push_back(2);
 int_values.push_back(3);
 cout << "Integer: " << sum(int_values) << endl;

 vector<double> double_values;
 double_values.push_back(1.5);
 double_values.push_back(2.1);
 cout << "Double: " << sum(double_values);

 return 0;
}

Upvotes: 3

xtofl
xtofl

Reputation: 41509

Indeed, there's no need to make it more stringent. Have a look at the string version (using the default constructor style advised by Chris Jester-Young) here...

Take care, too, for overflows - you might need a bigger type to contain intermediate results (or output results). Welcome to the realm of meta-programming, then :)

Upvotes: 1

kjensen
kjensen

Reputation: 39

You could look into type traits (use boost, wait for C++0x or create your own).

I found the following on google: http://artins.org/ben/programming/mactechgrp-artin-cpp-type-traits.pdf

Upvotes: 1

C. K. Young
C. K. Young

Reputation: 223003

Why would you want to restrict the types in this case? Templates allow "static duck typing", so anything allowed by what your sum function in this case should be allowed. Specifically, the only operation required of T is add-assignment and initialisation by 0, so any type that supports those two operations would work. That's the beauty of templates.

(If you changed your initialiser to T result = T(); or the like, then it would work for both numbers and strings, too.)

Upvotes: 1

Related Questions