2

我正在使用 Curiously Recurring Template Pattern 编写一些代码,其中派生类在编译时被传递以专门化其自己的基类。

有了这个,我遇到了一个烦人的问题,我为此做了一个玩具示例。我已经注释了 base::bar 并带有描述预期行为(以及为什么我期望它)的注释。整个示例旨在按原样编译,并导致 3 个编译器错误(clang-3.9),如注释中所述。供参考,$4.11/2:

“指向类型为 cv T 的 B 的成员的指针”类型的纯右值,其中 B 是类类型,可以转换为类型为“指向类型为 cv T 的 D 的成员的指针”类型的纯右值,其中 D 是派生类 ( B 的第 10 条)。如果 B 是 D 的不可访问(第 11 条)、模棱两可(10.2)或虚拟(10.1)基类,或 D 的虚拟基类的基类,则需要进行此转换的程序是格式不正确。转换的结果与发生转换之前的成员指针引用相同的成员,但它引用基类成员,就好像它是派生类的成员一样。结果引用了 D 的 B 实例中的成员。由于结果的类型为“指向类型为 cv T 的 D 成员的指针”,因此可以使用 D 对象取消引用。

#include <type_traits>

template<typename impl_t>
struct super
{
        typedef int impl_t::* member_dat_t;

    protected:
        template<member_dat_t dat>
        int foo()
        {
                return static_cast<impl_t *>(this)->*dat;
        }
};

template<typename impl_t>
struct base : super<impl_t>
{
        using this_t = base<impl_t>;
        int base_dat = 0;

        int bar()
        {
                // This, of course, succeeds
                this-> template foo<&impl_t::derived_dat>();

                // This fails during template instantiation, because the compiler knows that the
                // location of base_dat/base_func is in the base<derived> subobject of a derived
                // object rather than derived itself.
                this-> template foo<&impl_t::base_dat>();
                // (i.e., this succeeds, despite the address being taken on a member resolved off the impl_t)
                static_assert(std::is_same<int this_t::*, decltype((&impl_t::base_dat))>::value, "");

                // But what if we cast, as the standard (N3242) permits in $4.11/2 [conv.mem]
                // Now, these succeed
                static_assert(std::is_same<int impl_t::*, decltype((static_cast<int impl_t::*>(&impl_t::base_dat)))>::value, "");
                static_assert(std::is_same<typename super<impl_t>::member_dat_t, decltype((static_cast<int impl_t::*>(&impl_t::base_dat)))>::value, "");
                // But these still fail
                this-> template foo<static_cast<int impl_t::*>(&impl_t::base_dat)>();
                this-> template foo<static_cast<typename super<impl_t>::member_dat_t>(&impl_t::base_dat)>();
                return 1;
        }
};

struct derived : base<derived>
{
        int derived_dat;
};

void test()
{
        derived d;
        d.bar();
}

对于“为什么”:现有代码仅使用实际定义的成员base实例化。如果我也可以使用在 中定义、由 继承的成员,我会很高兴。实际代码使用成员函数,但对于示例来说成员数据就足够了。super::fooderivedbase<derived>derived

编辑:我在技术上提出了一个可行的解决方案。基本上,base派生自super<base<impl_t>>而不是super<impl_t>,所以现在我们可以调用foo的成员base<impl_t>,如果我们需要与最派生类不同的行为,我们可以将基类中的方法设为虚拟。但这抛弃了 CRTP 的一些好处(我们现在在对象中有 VTBL,以及动态多态的成本,即使我们在编译时知道我们想要什么)。

谢谢

4

0 回答 0