如果条件为真,如何仅实例化函数模板的一部分

52

在C++中,根据模板的类型,只构建代码的一部分是否可能?这将是类似于以下内容:

#include <iostream>

using namespace std;

template<typename T>
void printType(T param)
{
    #if T == char*
        cout << "char*" << endl;
    #elif T == int
        cout << "int" << endl;
    #else
        cout << "???" << endl;
    #endif
}

int main()
{
    printType("Hello world!");
    printType(1);
    return 0;
}
5个回答

58

类型特征:

#include <iostream>
#include <type_traits> // C++0x
//#include <tr1/type_traits> // C++03, use std::tr1

template<typename T>
void printType(T param)
{
  if(std::is_same<T,char*>::value)
        std::cout << "char*" << endl;
  else if(std::is_same<T,int>::value)
        std::cout << "int" << endl;
  else
        std::cout << "???" << endl;
}

甚至更好的方法是,重载这个函数:
template<class T>
void printType(T partam){
  std::cout << "???" << endl;
}

void printType(char* partam){
  std::cout << "char*" << endl;
}

void printType(int partam){
  std::cout << "int" << endl;
}

使用偏序关系可以确保调用正确的函数。此外,在一般情况下,重载函数比模板特化更可取,请参阅 这篇文章这篇文章 了解原因。如果您必须完全打印类型,则可能不适用于您,因为隐式转换会考虑已重载的函数。


2
+1 是因为提供了两种选择(并参考了Sutter对于模板特化的看法)。值得注意的是,第二种方法更加可扩展,因此“更好”。特别是对于自定义类型,ADL需要使用第二种方法。 - Matthieu M.
@Matthieu:是的,但正如我所说,如果一个人严格关注类型,它可能不适用,因为可能涉及隐式转换。或者如果你既有隐式转换又有备用模板,那么它是否具有歧义? - Xeo
1
如果需要隐式转换,那么我认为模板函数会更匹配。我对函数选择规则和转换序列有些困难 :) - Matthieu M.
请注意,在第一个示例中,std::is_same<T, char*>::value 不会处理字符串字面量,比如"Hello world!",因为它们会衰减为const char*而不是char*,而在第二个示例中,void printType(char* partam) 也不会接受字符串字面量。因此,你应该考虑在第一个示例中使用std::remove_cv<T>::type,在第二个示例中使用const char* - undefined

25

自从C++17以来,我们可以使用if-constexpr实现这一点。下面的代码可以在clang-3.9.1、gcc-7.1.0和最近的MSVC编译器19.11.25506上编译通过,同时需要添加选项/std:c++17。

#include <iostream>
#include <type_traits>

template<typename T>
void printType(T)
{
    if constexpr (std::is_same_v<T, const char*>)
        std::cout << "const char*" << std::endl;
    else if constexpr (std::is_same_v<T, int>)
        std::cout << "int" << std::endl;
    else
        std::cout << "???" << std::endl;
}

int main()
{
    printType("Hello world!");
    printType(1);
    printType(1.1);
    return 0;
}

输出:

const char*
int
???

谢谢!已修复并测试。实际上这更加强大,可以参考相关问题:https://dev59.com/eV4c5IYBdhLWcg3wT4xO。在那种情况下,如果没有constexpr,代码将无法编译。 - Wormer
1
只需注意T=char*T=const char*之间的区别。您可能需要使用std::remove_cv_t来处理两者。 - undefined

10

使用模板特化:


template<typename T>
void printType(T param)
{
   // code for the general case - or omit the definition to allow only the specialized types
}

template<>
void printType<char*>(char* param)
{
   // code for char*
}

template<>
void printType<int>(int param)
{
   // code for int    
}

// ...

8
Herb Sutter建议不要使用函数模板特化,而是直接使用常规的重载。详见http://www.gotw.ca/publications/mill17.htm。 - Matthieu M.

6
您可以使用一个专门化(specialization)来解决此问题。在所有模板之前,预处理器会运行,并且无法与它们进行交互。
template<typename T> void printType(T t) {
    std::cout << typeid(T).name(); // fallback
}
template<> void printType<char*>(char* ptr) {
    std::cout << "char*";
}
template<> void printType<int>(int val) {
    std::cout << "int";
}

1
Herb Sutter建议不要使用模板特化(在函数上),而是简单地使用常规重载。请参见http://www.gotw.ca/publications/mill17.htm。 - Matthieu M.
4
@Matthieu:我不在乎Sutter的建议。我的代码解决了OP的问题,即使他没有使用任何参数。重载绝对行不通。 - Puppy
感谢您对预处理器的解释。 - Congelli501
10
哇!让我们酷一点 :) 过载绝对解决了问题,就像Xeo所演示的那样。特化的问题在于,如果你在 char * 特化之后(突然)引入一个“ template <typename T> void printType(T* t); ”重载,那么 char *c; printType(c); 将调用这个新重载而不是“直观期望”的特化:http://ideone.com/F1xS7 - Matthieu M.

4
您可以使用模板规范来指定函数的版本以根据其类型进行不同的工作。例如,您可以创建一个通用版本的函数,可与大多数类型一起使用,并创建一个特定于例如int的版本,该版本将更快。您可以按照以下方式完成此操作:
template <class T>
void printType(T param)
{
    cout<<"Generic version"<<endl;
}
template <>
void printType<int>(int param)
{
    cout<<"Int version"<<endl;
}
template <>
void printType<char>(char param)
{
    cout<<"Char version"<<endl;
}
//Rince and repeat.

1
Herb Sutter建议不要使用模板特化(在函数上),而是简单地使用常规重载。请参见http://www.gotw.ca/publications/mill17.htm。 - Matthieu M.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接