专门针对大模板类中的单一方法

时间:2014-01-17 09:50:50

标签: c++ templates c++11

在C ++中,如果要部分地专门化模板类中的单个方法,则必须专门化整个类(如Template specialization of a single method from templated class with multiple template parameters中所述)

然而,当具有多个模板参数的较大模板类中,当它们中的每一个影响单个函数时,这变得令人厌烦。使用N个参数,您需要专门化2 ^ N次!

但是,对于C ++ 11,我认为可能有更优雅的解决方案,但我不确定如何处理它。也许以某种方式enable_if?有什么想法吗?

5 个答案:

答案 0 :(得分:13)

除了Torsten提出的基于继承的解决方案之外,您还可以使用std::enable_if和默认函数模板参数来启用/禁用该函数的某些特化。

例如:

template<typename T>
struct comparer
{
    template<typename U = T ,
    typename std::enable_if<std::is_floating_point<U>::value>::type* = nullptr>
    bool operator()( U lhs , U rhs )
    {
        return /* floating-point precision aware comparison */;
    }

    template<typename U = T ,
    typename std::enable_if<!std::is_floating_point<U>::value>::type* = nullptr>
    bool operator()( U lhs , U rhs )
    {
        return lhs == rhs;
    } 
};

我们利用SFINAE来禁用/启用函数的不同“特化”,具体取决于模板参数。因为SFINAE只能依赖于函数参数而不是类参数,所以我们需要一个函数的可选模板参数,它接受类的参数。

我更喜欢这种解决方案,而不是基于继承,因为:

  • 需要较少的输入。减少输入可能会减少错误。
  • 所有专业都写在课堂内。这种编写特化的方法包含原始类中的所有特化,并使特化看起来像函数重载,而不是基于棘手的模板的代码。

但是对于没有实现可选功能模板参数的编译器(如VS2012中的MSVC),此解决方案不起作用,您应该使用基于继承的解决方案。

编辑:您可以使用其他功能委托工作来覆盖模板函数的非实现的default-function-template-parameters:

template<typename T>
struct foo
{
private:
    template<typename U>
    void f()
    {
        ...
    }

public:
    void g()
    {
        f<T>();
    }
};

当然,编译器可以轻松地内联g()丢弃包装调用,因此这种替代方案没有性能损失。

答案 1 :(得分:5)

一个解决方案是从函数转发,你想重载一些依赖于类模板参数的实现:

template < typename T >
struct foo {
   void f();
};

template < typename T >
struct f_impl {
    static void impl()
    {
        // default implementation
    }
};

template <>
struct f_impl<int> {
    static void impl()
    {
         // special int implementation
    }
};

template < typename T >
void foo< T >::f()
{
    f_impl< T >::impl();
}

或者只使用私有函数,使用template参数调用它们并重载它们。

template < typename T >
class foo {
public:
    void f()
    {
        impl(T());
    }
private:
    template < typename G >
    void impl( const G& );
    void impl( int );
};

或者,如果它只是一种非常特殊的特殊情况,只需在实现中查询该类型。

答案 2 :(得分:4)

使用enable_if:

#include <iostream>
#include <type_traits>

template <typename T>
class A {
    private:
    template <typename U>
    static typename std::enable_if<std::is_same<U, char>::value, char>::type
    g() {
        std::cout << "char\n";
        return char();
    }

    template <typename U>
    static typename std::enable_if<std::is_same<U, int>::value, int>::type
    g() {
        std::cout << "int\n";
        return int();
    }

    public:
    static T f() { return g<T>(); }
};

int main(void)
{
    A<char>::f();
    A<int>::f();
    // error: no matching function for call to ‘A<double>::g()’
    // A<double>::f();
    return 0;
}

答案 3 :(得分:3)

标签调度通常是干净的方法。

在基本方法中,使用traits类来确定要调用的方法的子版本。这会生成一个描述决策结果的类型(称为标签)。

然后完美地转发传递标签类型实例的实现子版本。重载决策启动,只有你想要的实现被实例化和调用。

基于参数类型的重载分辨率是处理调度的一种不那么疯狂的方式,因为enable_if是脆弱的,在使用时很复杂,如果你有3+重载就变得非常复杂,并且有奇怪的可以通过精彩的编译错误让您惊讶的角落案例。

答案 4 :(得分:1)

也许我错了,但选择Manu343726提供的最好的anwser有一个错误并且不会编译。两个运算符重载都具有相同的签名。考虑问题std::enable_if : parameter vs template parameter

中最好的anwser

P.S。我会发表评论,但声誉不够,抱歉