众所周知,函数模板不能部分地专门用于C++。当您从概念上尝试实现此目标时,可以使用两种可能的解决方案。其中之一是使用带有静态函数的结构,可以选择将其与模板函数包装在一起,如下所示:

template <class T, class U>
struct BarHelper
{
    static void BarHelp(T t, const U& u)
    {
        std::cerr << "bar general\n";
    }
};

template <class T>
struct BarHelper<T, double>
{
    static void BarHelp(T t, const double& u)
    {
        std::cerr << "bar specialized\n";
    }
};
template <class T, class U>
void bar(T t, const U& u)
{
    BarHelper<T, U>::BarHelp(t, u);
};

此处的bar是可选的,如果您只喜欢直接使用该结构的静态成员,则可以(尽管您必须随后显式指定所有参数)。

另一种方法是重载函数模板:
template <class T, class U>
void func(T t, const U& u)
{
    std::cerr << "func general\n";

}
template <class T>
void func(T t, const double& u)
{
    std::cerr << "func specialized\n";
}

在我看来,第二种方法似乎更可取。对于初学者来说,它的详细程度要低得多,并且在意图方面要更加清楚(我们正在编写函数,因此让我们使用函数代替无意义的包装器结构)。此外,您还可以使用一些不错的技巧来控制重载分辨率。例如,您可以在继承层次结构中具有非模板化的“标记”参数,并使用隐式转换来控制功能的优先级。每当您在重载中具体指定一个类型时,您还可以获得隐式转换,如果您不喜欢这种行为,则可以在重载上使用enable_if来防止它(使您回到与结构相当的状态)。

有理由偏爱部分专业的结构吗?这些原因有多普遍? IE。哪个应该是您的“默认”?如果您:a)计划自己实现所有特化,而b)用作用户可以注入(inject)自己的行为的自定义点,这是否有所不同?

Herb Sutter有一篇关于避免功能模板专门化的著名博客文章。在其中,他还建议(接近尾声)偏向重载的函数模板,而不是部分专用的结构,但是他似乎没有给出任何具体的原因:http://www.gotw.ca/publications/mill17.htm



(添加了重点)。

最佳答案

让我们首先列出用于创建同一模板方法的多个变体的选项:


  • 简单重载:正如问题提到和演示的那样,这可以工作。
    但是,它并不总是能正常工作,我们将在下面看到。
  • 使用functor类局部特化:这是不具有模板功能特化的直接选择。
  • 结合使用std::enable_if和模板函数重载:当简单的模板重载不起作用时,可以选择此方法,请参见下文。


  • 使用基于模板的函数参数:Nir在评论中建议并在下面介绍的这种方法可实现模板函数重载,但在调用方需要一些麻烦的语法,请参见下文。



  • 该问题提出了一种情况,当从调用中推断出模板参数时,模板函数重载就可以正常工作。但是,在对模板函数的调用直接提供模板参数的情况下,并且需要根据模板参数的关系或条件来匹配实现时,重载将无济于事。

    考虑以下:
    template <typename T, T val1, T val2>
    void isSame1() {
        cout << "val1: " << val1 << ", val2: " << val2 << " are "
             << (val1==val2?" ":"NOT ") << "the same" << endl;
    }
    

    尽管val1和val2在编译时是已知的,但无法部分专门化在编译时我们知道它们相同的情况。函数重载在这种情况下无济于事,对于两个非类型模板参数具有相同值的情况,则没有重载。

    使用类局部特化,我们可以做到:
    template <typename T, T val1, T val2>
    struct IsSameHelper {
        static void isSame() {
            cout << "val1: " << val1 << ", val2: " << val2 << " are NOT the same" << endl;
        }
    };
    
    // partial specialization
    template <typename T, T val>
    struct IsSameHelper<T, val, val> {
        static void isSame() {
            cout << "val1: " << val << ", val2: " << val << " are the same" << endl;
        }
    };
    
    template <typename T, T val1, T val2>
    void isSame2() {
        IsSameHelper<T, val1, val2>::isSame();
    }
    

    或者,使用std::enable_if,我们可以执行以下操作:
    template<typename T, T val1, T val2>
    struct is_same_value : std::false_type {};
    
    template<typename T, T val>
    struct is_same_value<T, val, val> : std::true_type {};
    
    template <typename T, T val1, T val2>
    typename std::enable_if<!is_same_value<T, val1, val2>::value, void>::type isSame3() {
        cout << "val1: " << val1 << ", val2: " << val2 << " are NOT the same" << endl;
    }
    
    template <typename T, T val1, T val2>
    typename std::enable_if<is_same_value<T, val1, val2>::value, void>::type isSame3() {
        cout << "val1: " << val1 << ", val2: " << val2 << " are the same" << endl;
    }
    

    上面所有选项的主要内容如下:
    int global1 = 3;
    int global2 = 3;
    
    //======================================================
    // M A I N
    //======================================================
    int main() {
        isSame1<int, 3, 4>();
        isSame1<int, 3, 3>();
        isSame1<int*, &global1, &global1>();
        isSame1<int*, &global1, &global2>();
    
        isSame2<int, 3, 4>();
        isSame2<int, 3, 3>();
        isSame2<int*, &global1, &global1>();
        isSame2<int*, &global1, &global2>();
    
        isSame3<int, 3, 4>();
        isSame3<int, 3, 3>();
        isSame3<int*, &global1, &global1>();
        isSame3<int*, &global1, &global2>();
    }
    


    template <class T, T v> struct foo{
        static constexpr T val = v;
    };
    
    // in a .cpp
    template <class T, T v>
    constexpr T foo<T, v>::val; // required for non-integral / non-enum types
    
    template <class T, T v1, T v2> void isSame4(foo<T, v1> f1, foo<T, v2> f2) {
        cout << "val1: " << f1.val << ", val2: " << f2.val << " are NOT the same" << endl;
    }
    
    template <class T, T v> void isSame4(foo<T, v> f1, foo<T, v> f2) {
        cout << "val1: " << f1.val << ", val2: " << f2.val << " are the same" << endl;
    }
    

    此选项的主要内容如下:
    int global1 = 3;
    int global2 = 3;
    
    //======================================================
    // M A I N
    //======================================================
    int main() {
        isSame4(foo<int, 4>(), foo<int, 3>());
        isSame4(foo<int, 3>(), foo<int, 3>());
        isSame4(foo<int*, &global1>(), foo<int*, &global1>());
        isSame4(foo<int*, &global1>(), foo<int*, &global2>());
    }
    

    我看不到选项4的语法有任何优势。但是人们可以不这样认为...

    请注意,在选项4中需要一个.cpp文件来声明T foo::val,在所有其他选项中,所有内容都适用于.h文件。



    总结一下:

    在基于模板元编程的情况下能够获得编译时间分辨率的情况下,需要部分特化。这可以通过类局部专用化或使用enable_if(反过来需要其自身的类局部专用化来满足其条件)来实现。

    参见代码:http://coliru.stacked-crooked.com/a/65891b9a6d89e982

    关于c++ - 部分专用结构vs重载功能模板,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/37265188/

    10-17 01:40