【问题标题】:Simplify large number of template specializations简化大量模板特化
【发布时间】:2018-07-19 14:37:05
【问题描述】:

所以我有大量这个模板的模板特化:

template <typename T> // Same
struct foo { // Same
    using type_name = T; // Same
    foo(const int base) : _base(base) {} // May take other parameters
    void func(const T& param) {} // This function signature will be the same but body will differ
    int _base; // Same but may have more members
}; // Same

所以一个示例专业化是:

template<>
struct foo<float> {
    using type_name = T;
    foo(const int base, const int child) : _base(base), _child(child) {}
    void func(const T& param) { cout << param * _child << endl; }
    int _base;
    int _child;
};

显然这是一个玩具示例,_func 的主体将更多地参与其中。但我认为这表达了这个想法。我显然可以制作一个宏来帮助处理样板文件,并将该函数的专用版本的实现放在一个实现文件中。

但我希望 C++ 为我提供了一种无需宏就能做到这一点的方法。是否有另一种方法可以避免一遍又一遍地编写样板文件?

【问题讨论】:

  • // This function signature will be the same but body will differ 让我想到了继承而不是模板。
  • 这里实际上很少有样板文件。您可以定义 template &lt;typename T&gt; struct foo_base { using type_name = T; int _base; }; 并从中派生 foo 专业化 - 这将为每个专业化节省两行代码。
  • @IgorTandetnik 呃,我认为你是对的 :( 没有比这更好的了 T.T
  • @Ron 我想我一直认为它们是标题。

标签: c++ templates template-specialization boilerplate template-classes


【解决方案1】:

你可以对函数进行多个专业化,但不能对整个类进行专业化

喜欢这个

#include <iostream>
#include <string>

template<typename T>
struct foo {
    //common generic code
    using type_name = T;
    foo(const int base, const int child) : _base(base), _child(child) {}
    void func(const T& param);
    int _base;
    int _child;
};

template<>
void foo<float>::func(const type_name&) {
    //implementation
    std::cout << "float" << std::endl;
}

template<>
void foo<int>::func(const type_name&) {
    //implementation
    std::cout << "int" << std::endl;
}


int main() {
    foo<int> tint(0, 0);
    foo<float> fint(0, 0);

    tint.func(0);
    fint.func(0);
}

【讨论】:

  • 哇...我不知道您可以做到这一点...我将不得不进一步探索。
  • @JonathanMee 我认为您可以通过将构造函数和成员内容放入私有 CRTP 基类特化中并继承构造函数来分解。
  • @TimSeguine 假设我可以解决这个问题......我将如何解决构造函数问题?
  • @JonathanMee 我是在当天离开办公室之前直接写的。当我有机会时,我会用不那么晦涩的行话打出一个正确的解释作为答案。
【解决方案2】:

您可以使用一些轻量级的数据结构继承来帮助您将成员布局和构造函数定义与主模板的差异区分开来。

//Define an internal aggregate type you can specialize for your various template parameters
template <typename T>
struct foo_data {
    foo(const int base) : _base(base) {}
    int _base;
};

//Then derive privately from the data struct (or publicly if you really desire)
template <typename T>
struct foo : private foo_data<T> {
    using type_name = T;
    using foo_data<T>::foo_data<T>; //Make the base class constructors visible
    void func(const T& param); //Use member specialization as suggested by the other answer
};

我会让你决定这种方式是否更好,但结果是所有常见的部分都与所有不常见的部分完全分开。

在另一个答案下的评论中,我错误地将其描述为 CRTP。它不是,也没有 CRTP 的任何缺点。

如果您确实需要保留标准布局,则可以手动模拟继承,使用显式委托和完美转发。

template <typename T>
struct foo {
    using type_name = T;
    template <typename... Args>
    foo(Args&&... args) : base_data_(std::forward<Args>(args)...) {}
    void func(const T& param); //Use member specialization as suggested by the other answer
    foo_data<T> base_data_; 
};

一个缺点是我不认为委托构造函数会像所写的那样正确地使用 SFINAE,而且它还吃掉了noexcept 说明符和explicit。解决这些问题(如果需要)留给读者作为练习。

【讨论】:

  • 是的,当我开始尝试编写 CRTP 解决方案时,我发现它没有解决任何问题,反而增加了复杂性。这个带有模板化构造函数的解决方案实际上是满足我的需要的一个很好的解决方案。我不知道为什么我没有早点想到它。这个问题仍有进展,但当事情冷却下来时,我可能会接受这个。
  • Arg... 找到了问题所在。这个不允许我声明多个成员变量以对应于模板参数:(我需要进一步处理这个......也许我可以把这一切都放到对象的模板类型中......
  • 我无法弄清楚您的最后评论。根据您的意思,我认为您可以调整此解决方案来做到这一点。在 C++ 中做这类事情的最大问题是它没有得到很好的支持,而且做任何特定事情的语法舞蹈并不明显。您似乎真正想要的是 mixins。目前这意味着 CRTP 或其他一些模板黑魔法,但我寄予厚望,open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0847r0.html 将使我们的很多 mixin 嫉妒不那么痛苦。
【解决方案3】:

在实现模板化类型的特化时,没有很好的方法可以避免符号中的一些冗余。有一些技术可以避免实际代码的重复,例如

  1. 使用特征模板来提供特定类型的东西

    template<typename T>
    struct foo_traits { ... };  // provide many specialisations
    
    template<typename T>        // no specialisations
    struct foo
    {
        using traits = foo_traits<T>;
    
        template<typename...Aars>
        explicit foo(Args&&...args)
         : data(std::forward<Args>(args)...) {}
    
        int do_something_specific(T x)
        { return traits::do_something(data,x); }
      private:
        typename traits::data data;
    };
    
  2. 一种非常相似的方法是使用专门的基类:

    template<typename T>
    struct foo_base { ... };    // provide many specialisations
    
    template<typename T>        // no specialisations
    struct foo : foo_base<T>
    {
        using base = foo_base<T>;
    
        template<typename...Aars>
        explicit foo(int m, Args&&...args)
         : base(std::forward<Args>(args)...)
         , more_data(m) {}
    
        int do_something_specific(T x)
        { return base::do_something(x,more_data); }
      private:
        int more_data;
    };
    

    foo 的构造函数是一个可变参数模板,以便允许基类的构造函数接受任意数量和类型的参数。

  3. 您可以使用公共基类并专门化派生类。这可以通过Curiously recurring template pattern (CRTP)

    来完成
    template<typename Derived>
    struct foo_base            // no specializations
    {
        using type = typename Derived::type;
        int do_something(type x)
        {
            auto result = static_cast<Derived*>(this)->specific_method(x);
            return do_some_common_stuff(result);
        }
      protected:
        foo_base(type x) : data(x) {}
        type data;
      private:
        int do_some_common_stuff(type x)
        { /* ... */ }
    };
    
    template<typename T>       // some specialisations
    struct foo : foo_base<foo<T>>
    {
        using base = foo_base<foo>;
        using type = T;
        using common_type = typename base::common_type;
        using base::do_something;
        explicit foo(type x, type y)
          : base(x), extra_data(y) {}
      protected:
        type specific_method(type x)
        { /* ... */ }
      private:
        type extra_data;
    };
    

    请注意,foo_base 已经是一个模板(与普通多态的情况不同),因此您已经可以在那里做很多特定的事情。只有以不同方式完成的事情(不仅仅是不同类型)需要foo 的特化。

  4. 最后,您可以将这些方法结合起来,例如特征类与 CRTP。

所有这些方法都实现了某种类型的静态编译时多态,而不是真正的或动态多态:没有虚函数因此没有虚拟表,也没有表查找的开销。都是在编译时解决的。

【讨论】:

    【解决方案4】:

    这通常是通过继承来完成的——你把不可变的部分放到基类中,然后专门化子类。

    我认为您不需要为此提供示例,但如果您需要,请告诉我。

    【讨论】:

    • 我的意思是,我先尝试了继承,但我只需要重写构造函数,所以我刚刚为自己保存了int base 的声明:(
    • @JonathanMee 你能展示一个更接近你的实际代码的例子吗?如上所述,您的代码中几乎没有样板,因此您的专业化似乎很好。
    • @JonathanMee 正如 Igor 所提到的,您的专业化重复很少,任何尝试分解它都可能会创建比删除任何样板更多的样板。
    猜你喜欢
    • 2022-07-05
    • 1970-01-01
    • 2017-01-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-24
    • 1970-01-01
    相关资源
    最近更新 更多