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

问题描述 投票:0回答:1

这里我有一个简单的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?

c++ templates crtp
1个回答
3
投票

根本的区别是当您尝试访问

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{};
};

一般来说,基类是否可以使用

Impl
中的 typedef 或 constexpr?

这要看情况。您只能在

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
不必是完整类型。

© www.soinside.com 2019 - 2024. All rights reserved.