【问题标题】:Templated class specialization where template argument is a template模板参数是模板的模板类特化
【发布时间】:2011-05-10 13:12:07
【问题描述】:

我想知道类似的事情是否可能。基本上,我有一个模板类,它偶尔会采用模板类的对象。我想将它(或只是一个成员函数)专门用于特定的模板类,但该类的“通用”形式。

template<typename T, typename S>
class SomeRandomClass
{
    //put something here
};

template<typename T>
class MyTemplateClass
{
    void DoSomething(T & t) {
       //...something
    }
};

template<>
void MyTemplateClass< SomeRandomClass<???> >::DoSomething(SomeRandomClass<???> & t)
{
    //something specialized happens here
}

用适当的类型(双精度等)替换问号是可行的,但我希望它保持通用。我不知道该放什么,因为任何类型都不会被定义。我环顾四周,了解了模板模板参数,并尝试了各种组合均无济于事。感谢您的帮助!

【问题讨论】:

    标签: c++ templates


    【解决方案1】:

    可以像这样专门化这个类

    template <>
    template <typename T,typename S>
    class MyTemplateClass <SomeRandomClass<T,S> >
    {
        void DoSomething(SomeRandomClass<T,S>& t) { /* something */ }
    };
    

    只特化成员方法是不可能的,因为特化是对整个类的,你必须定义一个新的类。但是,您可以这样做

    template <>
    template <typename T,typename S>
    class MyTemplateClass <SomeRandomClass<T,S> >
    {
        void DoSomething(SomeRandomClass<T,S>& t);
    };
    
    template <>
    template <typename T,typename S>
    void MyTemplateClass<SomeRandomClass<T,S> >::DoSomething(SomeRandomClass<T,S>& t)
    {
        // something
    }
    

    将声明和定义分开。

    【讨论】:

    • 你能以同样的方式专门化一个模板化函数吗?
    • @Mirco 只是一个裸函数,而不是类的一部分?对于这种情况,您实际上并不需要初始的空模板,因为您没有要专门化的包含模板类。
    • 假设我有一个原型template &lt;typename T&gt; T fun(void);,我想将它专门用于所有类型,例如 std::list 类型。我可以得到类似template&lt;&gt; template&lt;typename S&gt; std::list&lt;S&gt; fun&lt;std::list&lt;S&gt;&gt;(void) { /*return sth*/ }; 的东西吗?
    • 只专注于返回类型?我不确定是否允许这样的部分专业化。但这将是一个发布并获得答案的好问题。
    【解决方案2】:

    我不完全确定为什么@Ryan Calhoun specialized the way he did 但这里有一个更简洁的例子:

    // class we want to specialize with later on
    template<typename T, typename S>
    struct SomeRandomClass
    {
        int myInt = 0;
    };
    
    // non-specialized class
    template<typename T>
    struct MyTemplateClass
    {
        void DoSomething(T & t) 
        {
           std::cout << "Not specialized" << std::endl;
        }
    };
    
    // specialized class
    template<typename T, typename S>
    struct MyTemplateClass< SomeRandomClass<T, S> >
    {
        void DoSomething(SomeRandomClass<T,S> & t) 
        {
           std::cout << "Specialized" << std::endl;
        }
    };
    

    您可以看到您不需要接受的答案中使用的冗余语法:

    template<>
    template<typename T, typename S>
    

    Working Demo


    另类

    您可以在非专业化类中使用 type_traits 和 tag-dispatch 来专门化函数。

    让我们先为is_random_class做一个概念:

    // concept to test for whether some type is SomeRandomClass<T,S>
    template<typename T>
    struct is_random_class : std::false_type{};
    
    template<typename T, typename S>
    struct is_random_class<SomeRandomClass<T,S>> : std::true_type{};
    

    然后让我们再次声明我们的MyTemplateClass,但这次不是模板化(因为我们没有专门化)所以我们称之为MyNonTemplatedClass

    class MyNonTemplatedClass
    {
        
        public:
        template<typename T>
        void DoSomething(T & t) 
        {
           DoSomethingHelper(t, typename is_random_class<T>::type());
        }
        // ...
    

    注意DoSomething 现在是如何模板化的,它实际上是在调用帮助程序而不是实现逻辑本身?

    让我们分解一下:

    DoSomethingHelper(t, typename is_random_class<T>::type());
    
    • t 和以前一样;我们传递T&amp; 类型的参数
    • typename is_random_class&lt;T&gt;::type()
      • is_random_class&lt;T&gt; 是我们的概念,因为它派生自 std::true_typestd::false_type,它将在类中定义一个 ::type(谷歌为“类型特征”)
      • ::type()“实例化”is_random_class&lt;T&gt;::type 指定的类型。我之所以用引号表示,是因为我们真的会像稍后看到的那样把它扔掉
      • typename 是必需的,因为编译器不知道 is_random_clas&lt;T&gt;::type 实际上命名了一个类型。

    现在我们准备看看MyNonTemplatedClass 的其余部分:

        private:
        //use tag dispatch. If the compiler is smart it won't actually try to instantiate the second param
        template<typename T>
        void DoSomethingHelper(T&t, std::true_type)
        {
            std::cout << "Called DoSomething with SomeRandomClass whose myInt member has value " << t.myInt << std::endl;
        }
        
        template<typename T>
        void DoSomethingHelper(T&t, std::false_type)
        {
            std::cout << "Called DoSomething with a type that is not SomeRandomClass\n";
        }
    };
    

    Full Working Demo v2 Here

    请注意,我们的辅助函数名称相同,但重载了第二个参数的类型。我们没有给参数命名,因为我们不需要它,希望编译器会优化它,同时仍然调用正确的函数。

    我们的概念仅在TSomeRandomClass 类型时才强制DoSomethingHelper(T&amp;t, std::true_type),并为任何其他类型调用另一个。

    标签调度的好处

    标签分派的主要好处是,如果您只想在该类中专门化单个函数,则无需专门化整个类。

    标签调度将在编译时发生,如果您尝试仅在 DoSomething 函数内对概念执行分支,您将无法获得。


    C++17

    C++17 中,使用变量模板 (C++14) 和 if constexpr (C++17),这个问题变得非常容易。

    我们使用我们的 type_trait 创建一个变量模板,如果提供的类型 TSomeRandomClass 类型,它将给我们一个 booltrue,否则为 false:

    template<class T>
    constexpr bool is_random_class_v = is_random_class<T>::value;
    

    然后,我们在 if constexpr 表达式中使用它,该表达式仅编译适当的分支(并在编译时丢弃另一个,因此检查是在 编译时,而不是运行时):

    struct MyNonTemplatedClass
    {
        template<class T>
        void DoSomething(T& t) 
        {
            if constexpr(is_random_class_v<T>)
                std::cout << "Called DoSomething with SomeRandomClass whose myInt member has value " << t.myInt << std::endl;
            else
                std::cout << "Called DoSomething with a type that is not SomeRandomClass\n";
        }
    };
    

    type-traits 是一种无需类特化即可模拟这种情况的方法。

    请注意,这里的is_random_class 是任意约束的替代。一般来说,如果您只检查单个非模板类型,则更喜欢普通重载,因为它在编译器上效率更高。

    Demo


    C++20

    C++20中,我们可以更进一步,通过在模板化成员函数上使用requires 子句来使用constraint 而不是if constexpr。缺点是我们再次回到两个函数;一个匹配约束,另一个不匹配:

    struct MyNonTemplatedClass
    {
        template<class T>  requires is_random_class_v<T>
        void DoSomething(T& t)
        {
            std::cout << "Called DoSomething with SomeRandomClass whose myInt member has value " << t.myInt << std::endl;
        }
        
        template<class T> requires !is_random_class_v<T>
        void DoSomething(T&) 
        {
            std::cout << "Called DoSomething with a type that is not SomeRandomClass\n";
        }
    };
    

    Demo


    同样在 C++ 20 中,我们可以显式编码一个概念并使用缩写模板语法:

    template<class T>
    concept IsRandomClass = is_random_class_v<T>;
    
    template<class T>
    concept IsNotRandomClass = !is_random_class_v<T>;
    
    // ...
    
    template<IsRandomClass T>
    void DoSomething(T& t)
    { /*...*/}
    
    template<IsNotRandomClass T>
    void DoSomething(T&)
    { /*...*/}
    

    Demo

    【讨论】:

    • 如果在 2010 年您希望您的代码在 rhel4 和 rhel5 上使用 gcc 编译,并在 windows xp 上使用 Visual Studio 2008 编译,这也是您编写它的方式。
    • 您给出了非常详细的答案。但是为什么我们需要 C++17 版本中的模板结构 is_random_class 呢?一些关于这方面的指针可能会受到赞赏
    • @KrisztiánSzegi:最终我们需要一种方法来检测T 是模板template&lt;class A, class B&gt; class SomeRandomClass 的实例化类型我们需要一种方法来“填充”A 的模板参数和B 因为模板在实例化之前不存在(也就是说,我们需要一个具体类型)。在template&lt;class T&gt;void DoSomething`的范围内,我们无法指定这些参数;我们只有T。所以我们想询问T,看看是否可以通过提供2个模板参数AB(代码中分别称为TS)来匹配它。 (续...)
    • @KrisztiánSzegi (cont from prev) 所以我们编写了类型特征 (is_random_class) 作为将 T 匹配到 SomeRandomClass&lt;A, B&gt; 的一种方式。主模板(仅接受单个模板参数)是所有不是SomeRandomClass&lt;A, B&gt; 实例的所有内容。下面的模板特化最终提供了两个模板参数来匹配AB,编译器将看到这种特化对于SomeRandomClass 的实例化类型是“更好”的匹配。希望对您有所帮助。
    • 那种答案,不清楚为什么我不能将 is_random_class_v 写为模板化的 constexpr 函数(一个包罗万象,一个专门针对 @987654387 @ 与结构一样),并在 DoSomething? 中调用 that? constexpr 变量 底层结构似乎一层抽象太多。
    【解决方案3】:

    您需要做的只是您想要保持通用性的模板。拿你开始的东西:

    template<typename T, typename S>
    void MyTemplateClass< SomeRandomClass<T,S> >::DoSomething(SomeRandomClass<T,S> & t)
    {
        //something specialized happens here
    }
    

    编辑:

    或者,如果您只想保留 SomeRandomClass 通用的一部分,您可以:

    template<typename T>
    void MyTemplateClass< SomeRandomClass<T,int> >::DoSomething(SomeRandomClass<T,int> & t)
    {
        //something specialized happens here
    }
    

    【讨论】:

    • 那些我认为差不多了,但是我无法编译它(使用 g++ 或 intel)。我认为将 template 放在那里会使编译器混淆,使其无法将其识别为专业化(即需要有一个 template ,对吗?)
    【解决方案4】:

    编辑:这是对另一个问题的正确答案。

    使用类型名T 两次会稍微混淆问题,因为它们是单独编译的,并且没有以任何方式连接。

    您可以重载该方法以获取模板化参数:

    template <typename T>
    class MyTemplateClass
    {
        void DoSomething(T& t) { }
    
        template <typename U,typename V>
        void DoSomething(SomeRandomClass<<U,V>& r) { }
    };
    

    这会将新方法中的UV 映射到SomeRandomClass 中的T'S'。在此设置中,UV 可能与 T 的类型相同,但不一定非要如此。根据您的编译器,您应该能够做到

    MyTemplateClass<string> mine;
    SomeRandomClass<int,double> random;
    
    // note: nevermind the non-const ref on the string literal here...
    mine.DoSomething("hello world");
    mine.DoSomething(random);
    

    模板化调用将被选为匹配重载,而无需显式重新指定类型。

    编辑:

    使用模板特化对DoSomething 的重载没有影响。如果您按以下方式对课程进行专业化

    template <>
    class SomeRandomClass <int,double>
    {
        // something here...
    };
    

    那么上面的重载会很高兴地吃掉这个专门的实现。只要确保专用模板和默认模板的接口匹配即可。

    如果您想要专门化 DoSomething 来为 SomeRandomClass 获取一对特定类型,那么您已经失去了普遍性……这就是专门化。

    【讨论】:

    • 不完全是。看起来它只是 DoSomething 成员的重载,而不是类本身的特化。
    • 你是对的,但是无论你传递的是专门的类还是常规的模板,方法重载都没有区别。
    • 我现在终于明白你在问什么了。这个答案完全落后。
    【解决方案5】:

    如果您想使用提供模板结构作为模板参数(打算在内部使用它)而不专门化它:

    这是一个示例,它在给定模板 sfinae 结构作为模板参数的情况下将类型附加到元组:

    template<typename Tuple, typename T, template<typename> class /*SFINAEPredicate*/>
            struct append_if;
    
            template<typename T, template<typename> class SFINAEPredicate, typename ... Types>
            struct append_if<std::tuple<Types...>, T, SFINAEPredicate>
            {
                using type = typename std::conditional<SFINAEPredicate<T>::value,
                        std::tuple<Types..., T>, std::tuple<Types...>>::type;
            };
    
    
        // usage
        using tuple_with_int = append_if<std::tuple<>, int, std::is_fundamental>;
    

    这可以从 C++11 开始使用。

    【讨论】:

      猜你喜欢
      • 2018-11-26
      • 2015-02-19
      • 2018-06-25
      • 2017-01-20
      • 2012-12-07
      • 1970-01-01
      • 2023-03-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多