【问题标题】:Constructors, templates and non-type parameters构造函数、模板和非类型参数
【发布时间】:2016-06-01 17:19:52
【问题描述】:

由于某些原因,我有一个类必须依赖于 int 模板参数。
出于同样的原因,该参数不能是类的参数列表的一部分,而是其构造函数的参数列表的一部分(当然是模板化的)。

问题出现了。
也许我遗漏了一些东西,但我看不到向构造函数提供这样一个参数的简单方法,因为它不能被推导或明确指定。

到目前为止,我找到了以下替代方案:

  • 将上述参数放入类的参数列表中

  • 创建一个可以调用的工厂方法或工厂函数,例如factory<42>(params)

  • 为构造函数提供一个traits结构

我尝试为最后提到的解决方案创建一个(不是那么)最小的工作示例,也是为了更好地解释问题。
示例中的类本身并不是模板类,关键是构造函数,反正真正的就是模板类。

#include<iostream>
#include<array>

template<int N>
struct traits {
    static constexpr int size = N;
};

class C final {
    struct B {
        virtual ~B() = default;
        virtual void foo() = 0;
    };

    template<int N>
    struct D: public B{
        void foo() {
            using namespace std;
            cout << N << endl;
        }

        std::array<int, N> arr;
    };

 public:
     template<typename T>
     explicit C(T) {
         b = new D<T::size>{};
     }

     ~C() { delete b; }

     void foo() { b->foo(); }

 private:
     B *b;
};

int main() {
    C c{traits<3>{}};
    c.foo();
}

说实话,上面提到的解决方案都不适合:

  • 将参数移动到类的参数列表中完全破坏了它的设计,不是一个可行的解决方案

  • 我想避免使用工厂方法,但它可以解决问题

  • traits struct 似乎是目前为止最好的解决方案,但不知何故我并不完全满意

这个问题很简单:有没有我遗漏的东西,可能是更简单、更优雅的解决方案,我完全忘记的语言细节,或者上面提到的三种方法是我必须选择的? 任何建议将不胜感激。

【问题讨论】:

  • 可以推导出来,但是是的,你需要一个标签类型——例如template&lt;int N&gt; explicit C(traits&lt;N&gt;);(其中traits可以是template&lt;int N&gt; using traits = std::integral_constant&lt;int, N&gt;;
  • 是的,这几乎就是我所做的。无论如何,如果我必须引入一个 traits 类,我也可以使用它来定义一些其他的东西,这就是为什么我没有使用像 integral_constant 这样的东西。
  • 在你的初始段落中,你说它既是模板参数又是构造函数的参数,这没有意义。还是这个矛盾让你感到困惑?
  • 我的意思是,如果它很容易推导出来,它将成为构造函数模板声明的参数列表的一部分,如template&lt;int N&gt; constructor(whatever, you, want)
  • 我不是 100% 清楚你在问什么,但有什么类型的擦除技巧会有所帮助吗?

标签: c++ templates constructor traits software-design


【解决方案1】:

使用模板特化和继承:

#include <iostream>
using namespace std;

template <int num> struct A {
    A() { cout << "generic" << endl; }
};

template <> struct A<1> {
    A() { cout << "implementation of 1" << endl; }
};

template <int num>
struct B : public A<num> {
    B() : A<num>() {}
};

int main(int argc, char *argv[])
{
    B<1> b;
    B<3> b1;
    B<4> b2;
}

编辑: 或者您可以更轻松:

template <int num>
struct A {
    A();
};

template <int num>
A<num>::A() { cout << "general " << num << endl; }

template <>
A<1>::A() { cout << "specialization for 1" << endl; }

【讨论】:

    【解决方案2】:

    我认为对于大多数情况而言,具有“特征”的解决方案是最好的。

    为了在这个问题中制造更多“混乱”,我将提供另外两个替代方案。也许在某些非常具体的情况下——它们可以在某些方面更好。


    1. 模板全局变量 - 您可以将其命名为原型解决方案

    class C 仅在其构造函数与您的原始代码不同:

    class C final {
        // All B and D defined as in OP code
     public:
        // Here the change - c-tor just accepts D<int> 
        template <int size>
        explicit C(D<size>* b) : b(b) {}
    
        // all the rest as in OP code
    };
    

    原型——模板全局变量:

    template <int N>
    C c{new C::D<N>()}; 
    // this variable should be rather const - but foo() is not const 
    // and copy semantic is not implemented...
    

    及用法:

    int main() {
        // you did not implement copy semantic properly - so just reference taken
        C& c = ::c<3>; 
        c.foo();
    }
    

    1. 基类解决方案 - 派生类取决于int

    这个解决方案虽然看起来很有希望,但我个人会避免 - 这只会使设计复杂化 - 而且这里也存在一些对象切片的可能性。

    class CBase {
        // all here as in OP code for C class
    public:
        // only difference is this constructor:
        template<int size>
        explicit CBase(D<size>* b) : b(b) {}
    };
    

    那么——最后一堂课:

    template <int N>
    class C final : private CBase {
    public:
        C() : CBase(new CBase::D<N>()) {}
        using CBase::foo;
    };
    

    用法:

    int main() {
        C<3> c;
        c.foo();
    }
    

    问:人们可以问,基类解决方案比仅仅添加int作为另一个参数更好。
    答:通过基本实现类,您不需要拥有许多相同代码的“副本” - 您可以避免模板代码膨胀...

    【讨论】:

      【解决方案3】:

      你必须传入一些可以推断的东西。最简单的使用方法是一个空的 int 包装器:std::integral_constant。既然你只想要ints 我相信,我们可以给它取别名,然后只接受那个特定的类型:

      template <int N>
      using int_ = std::integral_constant<int, N>;
      

      您的 C 构造函数只接受:

       template <int N>
       explicit C(int_<N> ) {
           b = new D<N>{};
       }
      
       C c{int_<3>{}};
      

      您甚至可以全力以赴并为此创建一个用户定义的文字(a la Boost.Hana),以便您可以编写:

      auto c = 3_c; // does the above
      

      另外,考虑简单地将特征转发到D。如果到处都是类型,元编程会更好。也就是说,在C中仍然接受相同的int_

      template <class T>
      explicit C(T ) {
          b = new D<T>{};
      }
      

      现在D 期望具有::value 的东西:

      template <class T>
      struct D: public B{
          static constexpr int N = T::value;
      
          void foo() {
              using namespace std;
              cout << N << endl;
          }
      
          std::array<int, N> arr;
      };
      

      C 的用户的角度来看,这两种方式都是一样的,但值得考虑。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-08-26
        • 1970-01-01
        • 1970-01-01
        • 2018-05-18
        • 2021-11-02
        • 1970-01-01
        相关资源
        最近更新 更多