【问题标题】:CRTP applied on a template classCRTP 应用于模板类
【发布时间】:2019-04-18 17:16:41
【问题描述】:

让我们考虑一个用于打印派生类的 CRTP 模板类 Print:

template <typename T>
struct Print {
    auto print() const -> void;
    auto self() const -> T const & {
        return static_cast<T const &>(*this);
    }

private:
    Print() {}
    ~Print() {}

    friend T;
};

因为我想基于派生类专门化打印,就像我们可以通过覆盖来做到这一点一样,所以我还没有实现该方法。

我们可以包装一个整数,例如:

class Integer :
    public Print<Integer>
{
public:
    Integer(int i) : m_i(i) {}

private:
    int m_i;

    friend Print<Integer>;
};

template <>
auto Print<Integer>::print() const -> void {
    std::cout << self().m_i << std::endl;
}

到目前为止,这有效,现在假设我想打印一个通用版本的包装器:

template <typename T>
class Wrapper :
  public Print<Wrapper<T>>
{
public:
    Wrapper(T value) : m_value(std::move(value)) {}

private:
    T m_value;

    friend Print<Wrapper<T>>;
};

如果我使用 Wrapper 的专门化来专门化我的打印方法,它会编译并工作:

template <>
auto Print<Wrapper<int>>::print() const -> void
{
  cout << self().m_value << endl;
}

但如果我想说“对于 Wrapper 的所有专业,都这样做”,它不起作用:

template <typename T>
auto Print<Wrapper<T>>::print() const -> void
{
  cout << self().m_value << endl;
}

如果我通过以下主函数运行它:

auto main(int, char**) -> int {
    auto i = Integer{5};
    i.print();

    auto wrapper = Wrapper<int>{5};
    wrapper.print();

    return 0;
}

编译器打印:

50:42: error: invalid use of incomplete type 'struct Print<Wrapper<T> >'
6:8: error: declaration of 'struct Print<Wrapper<T> >'

为什么?我怎样才能做到这一点 ?是否有可能或者我必须完全专业化我的 CRTP 课程?

【问题讨论】:

  • 你能发布一个完整的例子吗?我最初的预感是 auto self() const -&gt; T 是罪魁祸首,因为您实际上返回了整个对象。即使主体转换为引用返回也会复制对象。我懒得编译一个活生生的例子来测试。
  • 我很少使用尾随返回类型,但self 是否返回this 的副本?
  • 是的,你是对的,我做了一个副本,我没有注意,但是在 int 上,这无论如何也解决不了任何问题。不过我更正了它并添加了一个主要功能。
  • 尤达记法为什么喜欢你? auto print() const -&gt; void;
  • 我喜欢生锈...嗯,这几乎是唯一的原因:P

标签: c++ c++11 crtp


【解决方案1】:

只要你小心一点,你就可以绕道而行。

Live Demo

您的 Print 类将依赖另一个类 PrintImpl 进行打印。

#include <type_traits>

template<class...>
struct always_false : std::false_type{};

template<class T>
struct PrintImpl
{
    void operator()(const T&) const
    {
        static_assert(always_false<T>::value, "PrintImpl hasn't been specialized for T");
    }
};

您将为您的Wrapper 课程部分专门化此PrintImpl

template<class T>
struct PrintImpl<Wrapper<T>>
{
    void operator()(const Wrapper<T>& _val) const
    {
       std::cout << _val.m_value;
    }
};

并确保Wrapper 将这个PrintImpl 声明为friend

friend struct PrintImpl<Wrapper<T>>;

Print 类创建PrintImpl 的实例并调用operator()

void print() const
{
    PrintImpl<T>{}(self());
}

只要在您实际实例化 Print 类的实例之前声明您的特化,这就可以工作。


您也可以为您的Integer 类完全专门化PrintImpl&lt;T&gt;::operator(),而无需编写类专业化:

class Integer :
    public Print<Integer>
{
public:
    Integer(int i) : m_i(i) {}

private:
    int m_i;

    friend PrintImpl<Integer>;
};

template <>
void PrintImpl<Integer>::operator()(const Integer&  wrapper) const {
    std::cout << wrapper.m_i << std::endl;
}

【讨论】:

  • 这是一个不错的方法,不是每次调用函数时都创建 PrintImpl 有点重吗?
  • @Steranoid:这是一个必要的邪恶,因为你不能部分专门化函数。所以我们必须使用一个类。
  • 1.它可能已被优化掉。 2. 您可以将专用函数改为静态函数——它不必是operator()
  • 这里always_false的目的是什么?
  • @Steranoid:很公平,我明白你的意思。我认为我们可以从我的代码中获得您想要的完整功能专业化以及static_assert 的安全性(并且没有额外的继承):wandbox.org/permlink/VFPy7rTdXm9I3fYh
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-16
相关资源
最近更新 更多