在简单的 CRTP 案例中没有名为 "XXX" 的成员

No member named "XXX" in a simple CRTP case

这里我有一个简单的CRTP案例:

#include <cstddef>
#include <utility>

template <typename Impl>
class base
{
    constexpr static size_t impl_num = Impl::num;
};

template <typename Impl>
class deriv : public base<deriv<Impl>>
{
    friend class base<deriv<Impl>>;

    constexpr static size_t num = Impl::num_in;
};

class actual_impl
{
public:
    constexpr static size_t num_in = 10;
};

using my_type = deriv<actual_impl>;

int main()
{
    my_type a{};
}

这段代码编译得很好,但是当我将基 class 更改为:

#include <cstddef>
#include <utility>

template <typename Impl>
class base
{
    constexpr static std::make_index_sequence<Impl::num> idx{};
};

template <typename Impl>
class deriv : public base<deriv<Impl>>
{
    friend class base<deriv<Impl>>;

    constexpr static size_t num = Impl::num_in;
};

class actual_impl
{
public:
    constexpr static size_t num_in = 10;
};

using my_type = deriv<actual_impl>;

int main()
{
    my_type a{};
}

Clang 抱怨 error: no member named 'num' in 'deriv<actual_impl>'。我只是很困惑为什么第一种情况有效而不是第二种情况,这两种情况之间的根本区别是什么,因为在我看来,在这两种情况下 Impl::num_in 都用于基础 class.

一般来说,base class 是否可以使用来自 Impl 的 typedef 或 constexprs?

根本区别在于您尝试访问 Impl class 内部的那一刻。 base<Impl>中的Impl是一个不完整的类型,你可以用它做什么有一定的限制。

特别是,您不能访问 base 内的 num 数据成员,这就是为什么行

constexpr static std::make_index_sequence<Impl::num> idx{};

导致编译错误。请注意,要定义 base class,编译器必须在那一刻知道 Impl::num 的值。

与此相反,在您的第一个示例中,Impl::num 仅用于 初始化 impl_num,否则不依赖于在 Impl::num 上。该初始化的实例化发生在稍后,当 Impl 成为一个完整类型时。因此,没有错误。

如果稍微改变一下定义,

template<typename Impl>
class base {
    constexpr static decltype(Impl::num) impl_num = Impl::num;
    // or 
    constexpr static auto impl_num = Impl::num;
}

并使 impl_num type 依赖于 Impl,你会因同样的原因得到同样的错误。

添加间接没有帮助,下面的代码也编译失败:

template<typename Impl>
class base {
    constexpr static size_t impl_num = Impl::num;
    constexpr static std::make_index_sequence<impl_num> idx{};
};

In general, is it possible for base class to use typedefs or constexprs from Impl?

视情况而定。您只能在 Impl 是完整类型时发生实例化的上下文中使用它们。例如,

template<typename Impl>
class base {
public:
    void foo() {
        decltype(Impl::num) impl_num = 0;
    }
};

很好,但是

template<typename Impl>
class base {
public:
    decltype(Impl::num) foo() { 
        return 0;
    }
};

不是。

避免 CRTP 中不完整类型的潜在问题的标准技巧是引入辅助特征 class:

// Just forward declarations
template<typename Impl> class deriv;
class actual_impl;

using my_type = deriv<actual_impl>;

template<class> struct traits;
template<> struct traits<my_type> {
    using num_type = std::size_t;
};

template <typename Impl>
class base {
public:
    typename traits<Impl>::num_type foo() {
        return 0;
    }
};

// Now actual definitions
// ...

此处,要访问 traits<Impl> 内部结构,Impl 不必是完整类型。