基于c++模板参数的编译

c++ templates argument based compilation

本文关键字:编译 参数 c++ 基于      更新时间:2023-10-16

我想写一个模板函数,根据模板参数的类型调用不同的方法。除

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
   ...
}

基本上,GetData应该只调用基于t类型的一个方法,其他调用不应该存在。这有可能吗?

如果您需要整个函数对intdouble具有特定行为,则提供GetData的一些重载:

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

Call_X_Methods,如果只有一部分函数的逻辑要专门化:

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
   ....
}

请注意,当类型匹配时,重载解析将优先考虑非模板而不是函数模板,因此您可以确保为这两种类型调用正确的函数。

既然还没有人把它放在这里,我想我应该给出另一个选项(这可能是多余的,简单的专门化/重载更具可读性)。

你可以在c++ 11(或boost(或你自己的特殊类)中使用SFINAE(替换失败不是错误),如果你想要做的是选择满足特定标准的类型。而你的例子是,如果T是一个intT是一个double,如果我想使它比那更一般呢?我想选择如果T是任何整型,如果T是任何浮点型?

#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()
}

这是怎么回事?std::enable_if是一个只有typeval==true成员的模板。这使得std::enable_if<false,T>::type是一个无效的语句。编译器不会自动将其作为错误抛出。相反,它会寻找它可以做的其他事情。

std::is_floating_pointstd::is_integral只返回该类型的trait值。

老实说,我喜欢这比简单的专门化好一点,因为它更容易扩展:现在已经为unit8_t, long double, float等编写了函数。你仍然可以在此基础上进行专门化,如果其中任何一个需要有所不同的话。与重载相比,您可以更容易地显式选择要讨论的函数(如果您想获得函数指针,&foo<int>(int(*)(int))&foo更容易,更具可读性)。像所有模板方法一样,它是一个编译时操作,所以它不应该对运行时产生负面影响。

的缺点吗?好了,函数声明现在有99个字符长…

重载可能是这里的首选解决方案,更不用说因为它们给您带来了较低的维护负担:

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
}

或者,根据Call_(int|double)_Method的内部结构,您可以重载它们:

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

但是,如果您限制T的类型,使用重载,可能会更好地维护:

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

你也可以用类型特征来限制模板中的可实例化类型,但这可能会降低可维护性。

但也许你的情况不同,你有一个适用于所有类型的模板,除了一些精选的:

// 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> &) {....}

有很多选择,没有唯一的正确答案。可能给你一些可维护代码的经验法则是最好的:

不完整和不正确的Real Advices列表(_):

  • 模板可以很好,如果使用得当
  • 模板可以被恐怖地疯狂使用
  • 选择纯重载
  • 不时地重新考虑你的问题,并试着判断你当时选择的解决方案是否仍然是最有意义的。也就是说,保持灵活性,学习重构。
  • 总是争取可读性。

一个好的可读性检查是让你的代码休息几天,然后再检查一遍,看看你是否仍然知道你做了什么。如果一切正常,请在更长的时间间隔内重复此测试。