【问题标题】:Specializing single method in a big template class在大模板类中专门化单个方法
【发布时间】:2014-02-06 14:18:46
【问题描述】:

在 C++ 中,如果您想对模板类中的单个方法进行部分特化,则必须对整个类进行特化(如 Template specialization of a single method from templated class with multiple template parameters 中所述)

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

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

【问题讨论】:

    标签: c++ templates c++11


    【解决方案1】:

    除了 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),这个解决方案不起作用,你应该使用基于继承的解决方案。

    编辑:您可以使用其他委托工作的函数来包装模板函数的非实现默认函数模板参数:

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

    当然,编译器可以轻松内联 g() 丢弃包装调用,因此这种替代方案不会影响性能。

    【讨论】:

    • 根据stackoverflow.com/questions/14600201/… 中的讨论,您可以将 enable_if 作为第二个方法模板参数(在 U=T 之后)而不是函数返回类型。这使得函数签名更清晰,也应该与构造函数一起使用。
    • @CygnusX1 除了我知道那样,我总是使用返回类型的方式。但是,该解决方案更通用(如您所说与 ctors 一起使用),所以我不知道为什么我总是更喜欢返回类型的方式... :)
    【解决方案2】:

    一种解决方案是从函数转发,您希望重载到依赖于类模板参数的某些实现:

    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 < typename T >
    class foo {
    public:
        void f()
        {
            impl(T());
        }
    private:
        template < typename G >
        void impl( const G& );
        void impl( int );
    };
    

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

    【讨论】:

    • 当 ::impl() 引用主类时,这有点令人讨厌 - 你需要传递 'this' 指针并使侧结构成为朋友。此外,字段的存在可能以某种方式取决于 T (这就是您要专门化的原因)。如果您只是私下重载所有函数变体,它们可能无法编译某些 T。
    • 是的,第一个解决方案有其局限性。在第二个解决方案中,您不必为所有可能的模板类参数重载,第一个 impl() 函数的作用就像捕获所有默认值一样。如果根据模板类参数需要不同的“字段”,“通常”的解决方案是将它们放在基类中并完全专门化该基类。但这在某种程度上超出了原始问题的范围。
    【解决方案3】:

    使用 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;
    }
    

    【讨论】:

    • 我想你忘记添加U模板参数的默认值了。否则你的电话应该是A&lt;char&gt;::f&lt;char&gt;();,这是没有意义的。
    • @Manu343726 f 委托给 g(不需要默认值)
    • mmm 我从来没有想过将函数模板包装成不依赖于可选的模板参数。谢谢
    【解决方案4】:

    标签调度通常是执行此操作的简洁方式。

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

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

    基于参数类型的重载解析是处理调度的一种不那么疯狂的方式,因为enable_if 是脆弱的,在使用时很复杂,如果你有 3 次以上的重载,就会变得非常复杂,并且存在奇怪的极端情况编译错误会让您大吃一惊。

    【讨论】:

      【解决方案5】:

      也许我错了,但 Manu343726 提供的选择的最佳 anwser 有错误,无法编译。两个运算符重载都有相同的签名。考虑有问题的最佳答案std::enable_if : parameter vs template parameter

      附:我会发表评论,但没有足够的声誉,对不起

      【讨论】:

      • 第一个运算符有enable_if&lt;is_floating_point,另一个是enable_if&lt;!is_floating_point。在所有情况下,都将启用这两个运算符中的一个。
      • @CygnusX1 是的,在模板参数替换期间就是这种情况。但是,运算符重载的两个签名仍然完全相同,这对我来说意味着编译错误。如果我按原样粘贴此代码并在 gcc 4.8 下使用 -std=c++11 编译,我会收到一个错误。你没有?
      • 由于某种原因,我也遇到了编译器错误,因为由于某种原因没有触发 SFINAE。具有相同的签名是可以的,应该不是问题,因为其中一个版本应该被模板机制删除。我将解决方案更新为适用于我的编译器的版本(clang++ 3.4、g++ 4.8.3)
      • 好吧,我认为这是预期的行为。即使模板不会被实例化,或者它的默认参数之一确实无效,我们也不能有两个相同的定义。
      • SFINAE 导致其中一个定义不存在。你检查过更正的版本吗?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-04-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多