代码之家  ›  专栏  ›  技术社区  ›  BiagioF

带模板参数的模板专业化

  •  9
  • BiagioF  · 技术社区  · 7 年前

    template Foo

    template <typename T>
    class Foo {
      void foo();
    };
    

    我还有一个 样板 Bar (独立于第一个):

    template <int N>
    class Bar {};
    

    比如说,我想专门研究 foo() 无论什么方法 酒吧 班 我写错了:

    template <>
    template <int N>
    void Foo<Bar<N> >::foo() { /* ... */ }
    

    编译器责怪我,因为类型不完整:

    error: invalid use of incomplete type 'class Foo<Bar<N> >'
     void Foo<Bar<N> >::foo() { }
    

    Code

    C++98 ,但我想知道是否存在不同的解决方案 C++11 .


    笔记

    我可以解决整个班级的问题 对于泛型 酒吧

    Example Code

    这不是我想要的,我正在寻找(如果存在的话)更优雅的解决方案(C++98和C++11),它允许我专门研究和实现一个单一的类方法。


    编辑:

    The question on SO 没有解释如何专门使用模板参数。事实上,我的问题显示了编译器是如何对此抱怨的。

    4 回复  |  直到 7 年前
        1
  •  4
  •   max66    7 年前

    对于C++11,您可以使用SFINAE启用/禁用(使用 std::enable_if )两种不同版本的 foo() 内部a未专门化 Foo

    在C++98中,您没有 但是你可以模拟它(给我几分钟时间,我试着提出一个例子)。 抱歉:我的想法行不通,因为这种方法需要为C++11的创新方法使用默认模板参数。

    另一种方法是为定义模板基类 Foo() FooBase 插入 foo() (仅限) foo() )在 食物基础 专业化 食物基础 .

    另一种同样适用于C++98的方法是标记调度:您可以定义一个唯一的 foo() ,参数为零,则调用另一个 foo() ,参数由 T .

    下面是一个完整的(C++98可编译)示例

    #include <iostream>
    
    struct barWay   {};
    struct noBarWay {};
    
    template <int>
    struct Bar
     { };
    
    template <typename>
    struct selectType
     { typedef noBarWay type; };
    
    template <int N>
    struct selectType< Bar<N> >
     { typedef barWay type; };
    
    template <typename T>
    struct Foo
     {
       void foo (noBarWay const &)
        { std::cout << "not Bar version" << std::endl; }
    
       void foo (barWay const &)
        { std::cout << "Bar version" << std::endl; }
    
       void foo ()
        { foo(typename selectType<T>::type()); }
     };
    
    
    int main ()
     {
       Foo<int>        fi;
       Foo< Bar<42> >  fb;
    
       fi.foo();
       fb.foo(); 
     }
    
        2
  •  0
  •   Massimiliano Janes    7 年前

    如果不需要公共基础,另一种方法可以是为foo()提供一个自定义点,例如特征:

    template <typename T>
    struct foo_traits;
    
    template <typename T>
    struct Foo {
      void foo(){ foo_traits<T>::foo_cp(*this); }
    };
    
    template <typename T>
    struct foo_traits{ static void foo_cp(T&){/*default*/} };
    
    template <int N>
    class Bar {};
    
    template <int N>
    struct foo_traits<Bar<N>>{ static void foo_cp(Foo<Bar<N>>&){/*spec*/} };
    

        3
  •  0
  •   skypjack    7 年前

    如果你不能专业化 foo ,将其定义为将调用委托给内部 foo实现 班然后专门化该类。
    这样的代码应该在C++98中编译,与原始代码没有太大区别:

    template <typename T>
    class Foo {
        template<typename>
        struct FooImpl;
    
    public:
        void foo() { FooImpl<T>()(); }
    };
    
    template <int N>
    class Bar {};
    
    template <typename T>
    template <int N>
    struct Foo<T>::FooImpl< Bar<N> > {
        void operator()() { /* ... */ }
    };
    
    int main() {
        Foo< Bar<0> > fb;
        fb.foo();
    
        Foo<int> fi;
        //fi.foo();
    }
    

    最后一行没有按预期编译(至少我得到了预期的结果,只定义了 FooImpl 否则)。

    这样,您可以有选择地定义您想要的专业化 将导致编译错误。

        4
  •  0
  •   AndyG    7 年前

    我想知道是否存在不同的解决方案 C++11 .

    这是标记调度的典型用例,其中 max66

    我相信这里有一个比max66更干净的实现( running on godbolt

    template <class T>
    class Foo {
        template <class>
        struct tag{};
        template<class U>
        void foo_helper(tag<U>){std::cout << "default\n";}
        void foo_helper(tag<Bar<3> >){std::cout << "specialization for Bar<3>\n";}
    public:
        void foo(){return foo_helper(tag<T>());}
    };
    

    原理相同;不接受任何参数的客户机函数调用基于 T 论点然后,正常的过载会处理其余部分。

    只有在这里,我才使用模板化的catch-all方法。


    在C++11中,语法只会略有变化;我们可以说 tag<Bar<3>> 而不是 tag<Bar<3> > 因为新的解析规则允许嵌套模板使用V形。

    我们还可以制作标签和模板 foo_helper 一网打尽 variadic templates 更一般一点:

    template <class T>
    class Foo {
        template <class...>
        struct tag{};
        template<class... U>
        void foo_helper(tag<U...>){std::cout << "default\n";}
        void foo_helper(tag<Bar<3>>){std::cout << "specialization for Bar<3>\n";}
    public:
        void foo(){return foo_helper(tag<T>{});}
    };
    

    事实上,事情开始变得非常有趣 C++17 随着 constexpr if 这使得我们可以基于 T Live Demo ):

    template <class T>
    class Foo {
    public:
        void foo(){
            if constexpr (std::is_same_v<T, Bar<3>>){std::cout << "Specialization for Bar<3>\n";}
            else std::cout << "default\n";
        }
    };
    

    正如你所看到的,所有的标签内容都倾向于使用一个简单的if语句。

    我们利用 type_traits 在C++11中引入来检查 与我们想要的类型相反。这样的事情以前不一定行得通,因为所有的分支都需要编译。在C++17中,只编译(编译时)选择的分支。

    请注意,早在C++98中就可以通过使用 typeid ( godbolt demo ):

    void foo(){
        if (typeid(T) == typeid(Bar<3>)){std::cout << "Specialization for Bar<3>\n";}
        else std::cout << "default\n";
    }
    

    然而 类型ID 由于以下两个原因,该方法是一个糟糕的选择:

    1. 这是对编译时已知信息的运行时检查(慢)
    2. 它很脆弱,因为所有分支都必须针对所有模板实例化进行编译,而在C++17中 if constexpr 仅编译选定的分支。