在简单的 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
不必是完整类型。
这里我有一个简单的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
不必是完整类型。