这里我有一个简单的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{};
}
这个片段编译得很好,但是当我将基类更改为:
#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
都在基类中使用。
一般来说,基类是否可以使用
Impl
中的 typedef 或 constexpr?
根本的区别是当您尝试访问
Impl
类的内部时。 Impl
中的base<Impl>
是一个不完整的类型,并且你可以用它做什么有一定的限制。
特别是,您无法访问
num
内的 base
数据成员,这就是该行的原因
constexpr static std::make_index_sequence<Impl::num> idx{};
导致编译错误。请注意,要定义
base
类,编译器必须知道当时 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{};
};
一般来说,基类是否可以使用
中的 typedef 或 constexpr?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 中不完整类型潜在问题的标准技巧是引入辅助特征类:
// 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
不必是完整类型。