user3455320
user3455320

Reputation: 1

c++ templates argument based compilation

I want to write a template function which calls a different method based on type of template argument. Ex-

template<typename T>
void GetData(T& data)
{
   if(T is int)
      Call_Int_Method(data);
   else if(T is double)
      Call_Double_Method(data);   // This call will not compile if data is int
   ...
}

Basically, GetData should only be calling one method based on type of T. Other calls should not exist. Is it possible somehow?

Upvotes: 0

Views: 89

Answers (3)

Sebastian Mach
Sebastian Mach

Reputation: 39109

Overloads might be the preferred solution here, let alone because they put a lower maintenance burden on you:

void GetData(int& data)
{
   Call_Int_Method(data);
}

void GetData(double& data)
{
   Call_Double_Method(data);   // This call will not compile if data is int
}

Or, depending on the innards of Call_(int|double)_Method, maybe you can overload them:

template <typename T>
void GetData (T &data) {
    CallMethod (data);
}

void CallMethod (int &) {}
void CallMethod (double &) {}

But again, possibly is is better maintainable if you restrict the type of T, using overloading again:

void GetData (int &data) { CallMethod (data); }
void GetData (double &data) { CallMethod (data); }

You can also restrict the instantiable types in templates with type traits, but that might be less maintainable.

But maybe your situation is different, and you have a template that works for all types, except some cherry-picked ones:

// general template
template <typename T>
void GetData (T &data) {...}

// function, technically not an overload
void GetData (std::string &data) {....}

// this is an overload of the GetData(std::string&)-function:
void GetData (std::map<int,int> &) {....}

There is plenty to choose from and no single correct answer. Possibly giving you some rules of thumbs for maintainable code is best:

Incomplet and incorrekt list of Real Advices (ɔ):

  • Templates can be nice if used sanely
  • Templates can be horror is used insanely
  • Prefer plain overloads
  • Reconsider your problem from time to time and try to judge if you then-chosen solution is still what makes most sense. I.e., keep flexible and learn refactoring.
  • Always strive for readability.

A good readability check is to let your code rest for some days and review it again to see if you still know what you have done. If everything's okay, repeat this test at larger time intervals.

Upvotes: 1

IdeaHat
IdeaHat

Reputation: 7881

Since no one is putting this here yet, I thought I'd give another option (that may be overkill, simple specialization/overloading is much more readable).

You can use SFINAE (substution failure is not an error) in C++11 (or with boost (or with your own special classes)) if what you are looking to do is select types that meet a certain criteria. While your example is if T is an int or T is a double, what if I want to make it more general than that? I want to select if T is ANY integral type, and if T is ANY floating point type?

#include <string>
#include <iostream>
#include <type_traits>

template <typename T>
typename std::enable_if<std::is_floating_point<T>::value,T>::type foo(T val)
{
   std::cout << "floating function" << std::endl;
   return val;
}

template <typename T>
typename std::enable_if<std::is_integral<T>::value,T>::type foo(T val)
{
   std::cout << "integral function" << std::endl;
   return val;
}


int main(int argc,char** argv)
{
  foo(2.0);//T==double, floating point function
  foo(2);///T==int, integral function
  foo(2.0f);//T==float, floating point function
  //foo<std::string>(); throws no matching function for call to foo()
}

What is happening here? well std::enable_if is a template who ONLY has a member type iff val==true. This makes it so that std::enable_if<false,T>::type is an invalid statement. The compiler doesn't automatically throw this as an error. Instead, it looks for other things that it can do.

std::is_floating_point and std::is_integral simply return the trait values of that type.

Honestly, I like this a little better than simple specialization because it is more easily expandable: you've now allready written your function for unit8_t, long double, float, ect. You can still do specialization on top of this, if any of those need to be different. And compared to overloading, you can more easily explicitly select which function you're talking about (if you want to get the function pointer, &foo<int> is tons easier and more readable than (int(*)(int))&foo). Like all template methods, it is a compiler time operation, so it shouldn't negatively effect your run-time at all.

The downside? Well, the function declaration is now 99 characters long...

Upvotes: 1

juanchopanza
juanchopanza

Reputation: 227578

Provide some overloads, either of GetData if you need the whole function to have specific behaviour for int or double:

void GetData(int& data)
{
  Call_Int_Method(data);
}

void GetData(double& data)
{
  Call_Double_Method(data);
}

or the Call_X_Methods if only a portion of the function's logic is to be specialized:

template<typename T>
void DoTypeSpecificStuff(T& data) { /* stuff */ }

void DoTypeSpecificStuff(int& data) { .... }

void DoTypeSpecificStuff(double& data) { .... }

template<typename T>
void GetData(T& data)
{
   DoTypeSpecificStuff(data);

   // do other stuff
   ....
}

Note that overload resolution will favour non-templates over function templates when the types match, so you can be sure the right function will be called for these two types.

Upvotes: 8

Related Questions