【问题标题】:Class specialization based on the template argument's template argument基于模板参数的模板参数的类特化
【发布时间】:2023-03-12 18:16:01
【问题描述】:

我有一个从FooDerived<T> 派生的模板类Foo<T>,它又是模板类Bar<T> 的模板参数,即我最终得到类似Bar< FooDerived<T> > 的东西。

template <typename T>
class Foo
{
    public:
        typedef T type_t;

        virtual double compute() { return 1.0; }
};

template <typename T>
class FooDerived : public Foo<T>
{
    public:
        double compute() { return 42.0; }
};

template <typename T>
class Bar : public T
{
    public:
        void f()
        {
            // This function could vary depending on U (and possibly T)
            std::cout << "Bar< T<U> > : " << this->compute() << std::endl;
        }
};

我正在尝试根据FooDerived 的模板参数专门化Bar。例如,Bar&lt; FooDerived&lt;int&gt; &gt;Bar&lt; FooDerived&lt;float&gt; &gt; 将具有不同的行为。

int main(void)
{
    Bar< FooDerived<int> > a;
    a.f();
    Bar< FooDerived<float> > b;
    b.f();
}

如何在 C++03(或 C++11)中有效地实现这一点?高效,我的意思是我想避免无用的代码重复(实际程序涉及的类型和函数比本示例中的要多得多)。此外,使用代码的人应该能够在不需要修改代码的情况下添加他们的专业化,因此任何类型的switch-like 解决方案都不会被采用。

我一直在研究涉及boost::mpl 的类似 SFINAE 的解决方案,例如boost::mpl::if_boost::is_same 来检查类型,但似乎没有什么能达到我的目标。我想这可能不适用于那种模板专业化。我总是以error: redefinition of 'Bar' 结尾,因为编译器似乎并不认为它是一种专业化,例如,如果我尝试做这样的事情:

模板 类 Bar : public T, private boost::mpl::if_, int, boost::mpl::false_>

使用boost::mpl::if_,无论是作为私有继承还是作为额外的模板参数,似乎都无法实现特化。

那么实现这样的事情的正确方法是什么?

更新 1

专门化所有案例是可能的,但隐藏在这个例子背后的真正问题实际上要复杂得多。我有一个CachedFunction&lt;T&gt; : public T,其中T 派生自Function&lt;U&gt;(函数、可微函数、二次可微函数等),其中U 是一种存储类型(密集或稀疏矩阵)。 CachedFunction 包含大量函数,其实现取决于存储类型和函数类型。因此,元编程似乎是避免某些代码重复的好方法。

更新 2

针对所提供的答案:我试图避免对所有涉及的案例进行这些明确的模板专业化。试想一下,Foo 派生的类有 3 或 4 个,Foo 有 2 或 3 种类型,Bar 包含 6 或 7 个函数,需要根据 @ 的类型进行不同处理987654346@ 和考虑的派生类。基本上,对于每个ijk,我都需要实现:

template<> void Bar<FooDerived_i<Type_j> >::f_k(){ ... }

因此,我正在尝试看看是否还有其他“更清洁”的方式。

更新 3

如果我使用boost::is_same,我可以做这样的事情,但是这个解决方案使得在不修改代码的情况下处理新类型变得更加困难。

这是example

#include <iostream>
#include <boost/type_traits/is_same.hpp>

typedef int type1;
typedef float type2;

template <typename T>
class Foo
{
    public:
        typedef T type_t;

        virtual double compute() { return 1.0; }
};

template <typename T>
class FooDerived
{
    public:
        typedef T type_t;

        double compute() { return 42.0; }
};

template <class T>
class Bar : public T
{
public:
    void f()
    {
        // types have to be known...
        if (boost::is_same<typename T::type_t, type1>::value)
            std::cout << "Bar< T<type1> > : " << this->compute() << std::endl;
        else if (boost::is_same<typename T::type_t, type2>::value)
            std::cout << "Bar< T<type2> > : " << this->compute() << std::endl;
    }
};

int main(void)
{
    Bar< Foo<type1> > a;
    a.f();
    Bar< FooDerived<type2> > b;
    b.f();
}

【问题讨论】:

  • 您能否将尝试专门化Bar 的代码连同来自编译器的错误消息一起显示?
  • @arne:当然,尽管这显然是完全错误的。我会在一分钟内更新问题。
  • 你为什么不能只使用普通的专业化(没有任何 SFINAE 技巧)? IE。你定义了一个template &lt;&gt; class Bar&lt;FooDerived&lt;int&gt;&gt; 和一个template &lt;&gt; class Bar&lt;FooDerived&lt;float&gt;&gt;?
  • @jogojapan:因为问题实际上有点复杂。从Foo 派生的每个类都可能对函数有不同的实现,因此这意味着要为大量情况编写专门化(number of types like int/float * number of functions like name() * number of derived classes),从而重复大量代码。如果可能的话,我想避免这种情况。这就是为什么我认为元编程可能是答案。我将编辑问题以添加此信息。
  • 我明白了。所以你希望专业化只影响选定的成员函数,对吗?还有任何数据成员(或其类型)吗?

标签: c++ templates boost sfinae specialization


【解决方案1】:

(注意,这回答了问题的第一个版本 如果您认为应该删除,请通过 cmets 告诉我)

你可以这样做:

template <typename T>
class Bar : public T
{
    public:
        std::string name();
};

template<>
std::string Bar<FooDerived<int> >::name() {
  return std::string("int");
  }

template<>
std::string Bar<FooDerived<float> >::name() {
  return std::string("float");
  }

在这里测试:http://coliru.stacked-crooked.com/view?id=9054d3356f438b31b1adbb595620d838-ad7854d9cfd7979d567ca413f0830b65

感谢 jogojapan 的宝贵建议!

【讨论】:

    【解决方案2】:

    这个怎么样?

    template<>
    struct Bar< FooDerived<int> >
    {
       // int spezialisation here
    };
    
    template<>
    struct Bar< FooDerived<float> >
    {
       // float spezialisation here
    };
    

    对于只专门化一些具有不同行为但没有不同签名的函数,有以下方法:

    template<>
    std::string Bar<FooDerived<float> >::name() {
      return std::string("float");
    }
    

    更新:

    作为对您第二次更新的说明,我认为专业化是在这种情况下实现不同行为的唯一干净方法。但是,如果您以更精细的方式使用模板,您当然可以重用大量代码:

    template<class T>
    std::string name_impl(T& t) {
      // generic implementationm
    }
    
    std::string name_impl(Bar<FooDerived<float>& t) {
      // implementation
    }
    
    template<class T>
    std::string Bar<T>::name() {
      // something befor
      return name_impl(*this);
    }
    

    因此,您可以通过函数重载切换到编译时多态性(使用继承、enable_if、...),这可能比模板专业化更容易。这是编译时的template pattern

    编辑: 作为第三次更新,我建议阅读我的answer here。通过您的实现,您很可能会在方法f 中遇到问题。

    【讨论】:

    • @jogojapan 好的,我会改的。
    • 正如我在问题中所说,完全专业化一切是可能的,但我想避免重复大量代码。如果其他方法可行,这是我想避免的最坏情况解决方案。
    • 谢谢我没有编译它。
    • 很难从一个更复杂的问题中制作一个简单的例子,因此进行了多次更新以试图将答案引向正确的方向。基本上,这涉及this 类([...]FunctionGeneric[...]Function&lt;MatrixType&gt; 的类型定义)。我正在尝试为这些CachedFunctions 添加稀疏矩阵支持,同时对代码进行一些分解。我会调查您建议的解决方案并尽快回复您。谢谢!
    【解决方案3】:

    正如您在一条评论中所说,在实际示例中专门化该类会涉及大量代码重复,因此应避免。

    如果您想,正如您在问题中所说,“专门化” Foo(或 FooDerived,就此而言)的模板参数,您可能应该考虑在 Bar 中定义模板模板参数,否则您可以例如,肯定实例化一个 Bar,因此 Bar 的模板参数没有模板参数。

    你可以通过

    template <template<typename> class T, typename Arg> class Bar : public T<Arg> {
    public:
        std::string name()
        {
            return "Bar<T<Arg>>";
        }
    };
    

    也就是说,下面的代码展示了一个方法,可以用来获得专门的行为,而不需要专门化整个类

      1 #include <string>
      2 #include <iostream>
      3 
      4 namespace ExampleSpecializations{
      5         template <typename T>
      6         std::string name(){
      7                 return "generic";
      8         }       
      9         template <>
     10         std::string name<int>(){
     11                 return "int";
     12         }
     13 }
     14 
     15 template <typename Arg>
     16 struct Bar {
     17         std::string name()
     18         {
     19                 std::string ret = std::string("Bar<") + ExampleSpecializations::name<Arg>() + std::string(">");
     20                 return ret; 
     21         }       
     22 };      
     23 
     24 int main()
     25 {
     26         Bar<int> a;
     27         std::cout << a.name() << std::endl;
     28         Bar<char> b; 
     29         std::cout << b.name() << std::endl;
     30 }       
     31 
    

    请注意,使用外部函数(从第 4 行到第 13 行)也提供了良好的封装,因为您没有添加不需要访问相关类内部任何内容的成员函数。

      1 #include <string>
      2 #include <iostream>
      3 
      4 template <typename T>
      5 struct Foo1{
      6 };              
      7                         
      8 template <typename T>   
      9 struct Foo2{
     10 };              
     11 
     12 namespace ExampleSpecializations{
     13         template <typename T>
     14                 std::string name(const T& ){
     15                         return "generic"; 
     16                 }       
     17         template <>
     18                 std::string name(const int&){
     19                         return "int";
     20                 }       
     21         template <typename T>
     22                 std::string name(const Foo1<T>& ){
     23                         std::string  ret = "Foo1<" + ExampleSpecializations::name<T>(T()) + ">";
     24                         return ret;  
     25                 }       
     26         template <typename T>
     27                 std::string name(const Foo2<T>& ){
     28                         std::string  ret = "Foo2<" + ExampleSpecializations::name<T>(T()) + ">";
     29                         return ret;  
     30                 }       
     31 }               
     32 
     33 template <typename Arg>
     34 struct Bar {
     35         std::string name()
     36         {
     37                 std::string ret = std::string("Bar<") + ExampleSpecializations::name<>(Arg()) + std::string(">");
     38                 return ret; 
     39         }       
     40 };      
     41 
     42 int main()
     43 {
     44         Bar<int> a;
     45         std::cout << a.name() << std::endl;
     46         Bar<char> b; 
     47         std::cout << b.name() << std::endl;
     48         
     49         Foo1<int> f1i;
     50         std::cout << ExampleSpecializations::name(f1i) << std::endl;
     51         
     52         Foo2<int> f2i;
     53         std::cout << ExampleSpecializations::name(f2i) << std::endl;
     54         
     55         Bar<Foo2<int> > bf2i;
     56         std::cout << bf2i.name() << std::endl;
     57 }
    

    try the code here 不用说,我建议也将 Bar::name 设为非成员函数。

    最后,如果您不想提供“通用”行为,则根本不提供默认实现,如下所示:

            template <typename T>
                std::string name(const T& );
    

    通过这种方式,前面的代码会发出错误,因为在 Bar 上调用了“name”方法。该错误通过注释掉第 47 行或实现 char 特化来解决。

    【讨论】:

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