【问题标题】:Specializing Template Classes with Parameter Packs使用参数包专门化模板类
【发布时间】:2020-02-06 22:01:36
【问题描述】:

我将继承与一组类一起使用。其中一个子类接受std::function(ReturnTy<ParamTypes...>) 以及ParamTypes 参数。类签名和构造函数如下所示:

template<class ReturnTy, class... ParamTypes>
class Child : public Interface
{
public:
    Child(ReturnTy default_value, ParamTypes... args)
        : func_set_(false)
        , m_Args(std::make_tuple(std::forward<ParamTypes>(args)...))
        , m_ReturnValue(default_value)
    {}

private:
    bool func_set_;
    std::function<ReturnTy(ParamTypes...)> m_Funciton;
    std::tuple<ParamTypes...> m_Args;
    ReturnTy m_ReturnValue;
};

我的问题是当我想专门针对没有参数的情况。此外,我还想专门针对ReturnTy=void 并且有参数的情况。我找到了一个接近我正在寻找的答案here,但它并没有完全涵盖我正在尝试做的事情,因为该问题使用编译时整数作为模板参数,而我正在使用类型.它还涉及函数而不是类。我觉得我很接近了,但我只需要一些帮助来让我的代码正常工作。

作为参考,以下是我对其他专业的了解(缩短):

template<class ReturnTy>
class Child<ReturnTy> : public Interface
{
public:
    Child(ReturnTy default_value)
        : // The same as first class without m_Args
    {}

private:
    // Same as first class without m_Args
};

template<class... ParamTypes>
class Child<void, ParamTypes...> : public Interface
{
public:
    Child(ParamTypes... args)
        : // Same as first class without m_ReturnValue

private:
    // Same as first class without m_ReturnValue
};



编辑
具体来说,问题来自类似以下代码行:

Child<void> obj1(5);

【问题讨论】:

  • 大家都知道,我已经从类中省略了与我的问题无关的方法。例如,有一个set_function() 方法将初始化std::function,还有一个来自接口的虚拟run() 函数执行m_ReturnValue = std::apply(m_Function, m_Args)。
  • 如果是Child&lt;void&gt;,应该选择哪个专业?仅带有ReturnTy 的那个还是带有void 的那个?
  • @max66 我想要只选择void,但我不知道如何告诉编译器选择那个。
  • 对不起:我不清楚:你的意思是最后一个:class Child&lt;void, ParamTypes...&gt;?

标签: c++ class templates variadic-templates template-specialization


【解决方案1】:

问题是你的专长是同一级别的(没有人比另一个更专精)并且Child&lt;void&gt; 两者都匹配。

如果您希望Child&lt;void&gt; 匹配Child&lt;ReturnTy&gt; 的情况(否则解决方案简单而优雅:在第二个特化中,将ParamTypes... 列表拆分为Par0 强制类型和@987654327 的其余部分@) 我没有看到一个简单而优雅的解决方案。

目前,我能想到的最好的方法是添加一个间接级别(添加一个 Child_base 类),还添加一个模板参数以明确所需的解决方案。

也许可以用更简单的方式制作(抱歉,此时我可以尝试使用编译器)但我想如下所示

template <typename RT, bool, typename ... PTs>
class Child_base : public Interface
{
   // general case (no empy PTs... list and no void return type)
};

template <typename ... PTs>
class Child_base<void, true, PTs...> : public Interface
{
   // case return type is void (also empy PTs... list)
};

template <typename RT>
class Child_base<RT, false> : public Interface
{
   // case return type only, but not void, and empy PTs
};

template <typename RT, typename ... PTs>
class Child 
   : public Child_base<RT, std::is_same_v<void, RT>, PTs...> 
 {
 };

这样,Child&lt;void&gt; 继承自匹配 Child_base 的第一个特化但不匹配第二个特化的 Child_base&lt;void, true&gt;。

我提出关于Child 的另一种方式:不要将其定义为从Child_base 派生的类,您可以尝试将其定义为using 的别名Child_base

template <typename RT, typename ... PTs>
using Child = Child_base<RT, std::is_same_v<void, RT>, PTs...>;

也许用更合适的名称重命名Child_base。

【讨论】:

  • 抱歉这个愚蠢的问题,但我是否仍需要为Child 指定一个构造函数,还是默认使用来自Child_base 的构造函数?
  • @DrakeJohnson - 根本不是一个愚蠢的问题,恕我直言:这有点复杂。我不是专家,但据我所知,如果“使用”是指派生类可以显式调用基类构造函数(如果可访问)来初始化基类,是的:它可以。并且默认基类构造函数可以默认初始化派生类中的基组件。但是如果对于“使用”你的意思是基类可以从派生类继承并用作派生类的构造函数,答案是:默认情况下不,是使用using(但仅限using从 C++11 开始)(继续
  • @DrakeJohnson - 无论如何,如果您阅读cpp reference using declaration page 中的“继承构造函数”部分,我想这会更有用
  • @DrakeJohnson - 无论如何,我已经改进了我的答案,添加了一个使用 using 的示例,应该完全消除继承问题
  • @DrakeJohnson - 抱歉:在我的第一条评论中,“但是如果对于“使用”,您的意思是基类可以从派生类继承并用作派生类的构造函数”是错误:应该是“但是如果为”used”是不是表示基类constructor可以从派生类继承并用作派生类的构造函数”
【解决方案2】:

问题是 Child&lt;void&gt; 匹配 2 个(部分)专业化(其中没有一个比另一个更专业化):

  • template&lt;class ReturnTy&gt; class Child&lt;ReturnTy&gt; 和 [ReturnTy = void]
  • template&lt;class... ParamTypes&gt; class Child&lt;void, ParamTypes...&gt; 空包。

你需要额外的专业化:

template<>
class Child<void> : public Interface
{
public:
    Child() = default;

// ....
private:
    std::function<void()> m_Function;
};

Demo

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多