【问题标题】:Class Template specialization for multiple types多种类型的类模板特化
【发布时间】:2019-08-20 14:30:52
【问题描述】:

我发现了一些问题,它们提出了类似的问题,但找不到针对我的特定案例的直接答案。 模板的整个语法让我很困惑,所以我可能只是误解了一些东西。

我有一个应该接受所有类型的类模板。 简单例子:

template <class T>
class State {
  public:
    void set(T newState);
    T get();
  private:
    T state;
};

template <class T>
void State<T>::set(T newState){
  state = newState;
}

template <class T>
T State<T>::get(){
  return state;
}

现在我想为一组类型创建一个专门的模板,为这些类型添加额外的功能。从我目前发现的情况来看,我可以使用所谓的 type_traits,但它们究竟是如何用于实现这一点的对我来说仍然是一个谜。

F.e.这种针对 int 类型的特化,而不是仅仅为 int 类型编写,我还希望允许所有其他 int 和 float 变体。我找到了 std::is_arithmetic 但不知道如何利用它来实现这一点。

template <>
class State <int> {
  public:
    void set(int newState);
    int get();
    int multiplyState(int n);
  private:
    int state;
};

void State<int>::set(int newState){
  state = newState;
}

int State<int>::get(){
  return state;
}

int State<int>::multiplyState(int n){
  return state*n;
}

【问题讨论】:

  • 查找 std::enable_ifsfinae部分专业化 ;)

标签: c++ c++11 templates


【解决方案1】:

您可以将部分模板专业化与 SFINAE 结合使用来实现:

#include <type_traits>

template <class T, typename = void>
class State
{
    T state;

public:
    void set(T newState)
    {
        state = newState;
    }

    T get()
    {
      return state;
    }
};

template <typename T>
class State<T, std::enable_if_t<std::is_arithmetic_v<T>>>
{
    T state;

public:
    void set(int newState)
    {
        state = newState;
    }

    int get()
    {
        return state;
    }

    int multiplyState(int n)
    {
        return state*n;
    }
};

live example here

这里的诀窍在于使用第二个模板参数(可以不命名并给出默认参数)。当您使用类模板的特化时,例如State&lt;some_type&gt;,编译器必须确定应该使用哪个模板。为此,它必须以某种方式将给定的模板参数与每个模板进行比较,并确定哪一个是最佳匹配。

这种匹配的实际完成方式是尝试从给定的模板参数中推断出每个部分特化的参数。例如,在State&lt;int&gt; 的情况下,模板参数将是intvoid(后者是因为主模板的第二个参数的默认参数)。然后我们尝试推断出我们唯一的偏特化的论点

template <typename T>
class State<T, std::enable_if_t<std::is_arithmetic_v<T>>>;

来自模板参数int, void。我们的偏特化有一个参数T,可以直接从第一个模板参数推导出为int。至此,我们已经完成了所有参数的推导(这里只有一个)。现在我们将推导的参数代入偏特化:State&lt;T, std::enable_if_t&lt;std::is_arithmetic_v&lt;T&gt;&gt;&gt;。我们最终得到State&lt;int, void&gt;,它与int, void 的初始参数列表相匹配。因此,部分模板特化适用。

现在,如果我们写了State&lt;some_type&gt;,其中some_type 不是算术类型,那么在我们成功推导出部分特化的参数之前,过程将是相同的some_type。同样,我们将参数替换回部分特化State&lt;T, std::enable_if_t&lt;std::is_arithmetic_v&lt;T&gt;&gt;&gt;。但是,std::is_arithmetic_v&lt;some_type&gt; 现在将变为false,这将导致std::enable_if_t&lt;…&gt; 未被定义并且替换失败。由于substituion failure is not an error 在此上下文中,这仅意味着此处不能选择部分特化,而是将使用主模板。

如果有多个匹配的部分专业化,则必须对它们进行排名以选择最佳匹配。 The actual process 相当复杂,但通常归结为选择最具体的专业。

【讨论】:

  • 您能解释一下&lt;class T, typename = void&gt;class State &lt;T, std::enable_if_t&lt;std::is_arithmetic_v&lt;T&gt;&gt;&gt; 的第二个参数(?) 的含义吗?我很难找到对这种语法的良好解释。
  • @PTS 我用希望是有用的解释更新了我的答案。
  • 感谢您的进一步解释。不幸的是,这不能用 C++11 编译。我尝试了enable_ifis_arithmetic 而不是godbolt.org/z/VocB39,但这似乎无效。这需要 C++17 还是必须为 C++11 以不同方式构造?
  • 这个godbolt.org/z/1qgSY1 似乎可以编译,但没有使用特化,而是使用了基本模板。
  • 我的示例确实需要 C++17。对于 C++11,您必须使用 typename std::enable_if&lt;std::is_arithmetic&lt;T&gt;::value&gt;::type&gt;
【解决方案2】:

虽然对于像这样的小例子来说,专门化整个类是很好的,但在更复杂的情况下,您可能有兴趣避免复制所有成员,这样您就可以将一个成员添加到专门化中。为此,一种常见的技术是从公共基类继承额外的成员函数,并仅将基类专门化为具有或不具有成员。您必须使用 CRTP,以便基类成员函数知道如何访问派生类。这看起来像:

// StateBase only contains the extra multiplyState member when State tells it to
// define it, based on T being an arithmetic type
template <class D, class T, bool has_multiply>
struct StateBase {};

template <class D, class T>
struct StateBase<D, T, true> {
    T multiplyState(int n) {
        return static_cast<D*>(this)->state * n;
    }
};

template <class T>
class State : public StateBase<State<T>, T, std::is_arithmetic<T>::value> {
  public:
    // no need to duplicate these declarations and definitions
    void set(T newState);
    T get();
  private:
    // note that we write State::StateBase to force the injected-class-name to be found
    friend struct State::StateBase;
    T state;
};

Coliru link

【讨论】:

  • 谢谢,这确实是我的下一个问题。
  • 你能在这里解释一下结构的使用吗?这对我来说不是一个熟悉的概念,但我看到结构与类模板一起使用很多。这是否像创建由编译器插入到实际类定义中的类的部分部分一样工作?
  • @PTS 结构只是一个默认对其成员和基类进行公共访问的类。
  • 哦对了,我记得很模糊。我不知道 C++ 中的朋友的概念 :)
猜你喜欢
  • 2013-07-11
  • 2017-09-20
  • 1970-01-01
  • 1970-01-01
  • 2021-12-28
  • 1970-01-01
  • 1970-01-01
  • 2018-05-09
相关资源
最近更新 更多