【问题标题】:CRTP - visibility of the type of a nested leaf classCRTP - 嵌套叶类类型的可见性
【发布时间】:2017-06-26 11:54:14
【问题描述】:

我想了解是否可以在基 CRTP 类中使用叶 CRTP 类的嵌套类。下面的示例演示了该问题。

#include <iostream>

using namespace std;

template<class T>
class A
{

protected:
    T* asLeaf(void)
        {return static_cast<T*>(this);}
    T const* asLeaf(void) const
        {return static_cast<T const*>(this);}
public:

    struct Inner
    {int a = 10;};

    void dispInner(void) const
        {std::cout << asLeaf()->inner.a << std::endl;}

    // I would like to use T::Inner in this class, e.g.
    // typename T::Inner mvInnerA;
    // However, I understand that it is not possible to
    // use it in the form that is stated above. Thus, 
    // I am looking for any possible workarounds. 

};


class B: public A<B>
{
public:
    struct Inner: public A<B>::Inner
    {int b = 20;};

protected:
    friend A<B>;
    B::Inner inner;

public:
    void dispInner(void) const
        {
            A<B>::dispInner();
            std::cout << asLeaf()->inner.b << std::endl;
        }
};


int main()
{

    B b;
    b.dispInner();

    return 0;

}

编辑

我想根据收到的反馈再提供几个 cmet:

  • 我知道我可能没有使用足够的设计实践。特别是A是否应该知道inner的存在可能会受到质疑。但是,我想在A 中定义B::Inner 类型的对象inner,而不是在B 中提供inner 的定义并在A 中使用它。
  • 我知道我无法转发声明 B 和/或 B::Inner 以及无法转发的原因。因此,从技术上讲,设计问题没有解决方案。不过,我正在寻找可行的解决方法。

我已经考虑了几种替代解决方案:

  • 一种可能的可行解决方案是不要尝试在A 中“定义”B::Inner inner,并使用A 的成员函数来提供允许修改@987654337 的A&lt;B&gt;::Inner 部分的功能@。
  • 另一种可能的解决方案是明确定义A&lt;B&gt;::InnerB::Inner 类(即不作为嵌套类)。但是,我宁愿避免这种情况,因为根据设计,任何不派生自 A 的类都不需要与 A&lt;B&gt;::Inner 或派生自 A&lt;B&gt;::Inner 的类交互

我提出的两种解决方案都可以接受。但是,我正在寻找任何可行的替代方案。

【问题讨论】:

  • 一般规则是你的基类不应该知道它的孩子,你正在引入循环依赖,这是不好的
  • @lapinozz 这就是他使用 CRTP (en.wikipedia.org/wiki/Curiously_recurring_template_pattern) 的原因,这并没有错。
  • @lapinozz - 您正在考虑动态多态性,它与 CRTP 的静态多态性完全正交。不一定适用相同的“规则”。
  • 我明白了,我的错,但是问题出在哪里?注释代码似乎很好,错误是什么?
  • @lapinozz 感谢您的评论。我相信您在回答我的问题的原始规范时所说的内容是正确的。但是,请参阅我在问题末尾制作的 cmets。

标签: c++ inner-classes forward-declaration crtp


【解决方案1】:

标准说:

在类说明符的结尾} 处,类被视为完全定义的对象类型(或完整类型)。

当您将A 特化为A&lt;B&gt; 时,B 不是一个完全定义的对象。因此,您不能期望能够从A 的定义中访问其成员或类型或任何内容(即使您可以从A 的成员方法的定义中回调派生类,即完全合法,而不是 CRTP 习语的目的)。
换句话说,当你这样做时:

typename T::Inner mvInnerA

您无法保证 T 是一个完全定义的对象,这就是您收到错误的原因。


一些替代方案:

  1. 您可以将mvInnerType 定义为函数而不是类型,并将其用作工厂来创建T::inner 类型的对象:

    [static] auto mvInnerA() {
        return typename T::Inner{};
    }
    

    将其用作:

    auto foo = A<B>::mvInnerA();
    

    或者:

    auto foo = obj.mvInnerA();
    

    正确的形式取决于您是否使用static
    请注意,您仍然可以以某种方式使用隐藏类型,即使它的名称不可访问:

    using HiddenType = decltype(A<B>::mvInnerA());
    HiddenType foo = A<B>::mvInnerA();
    
  2. 您可以使用模板定义mvInnerA 的别名声明,如下所示:

    template<typename U = T>
    using mvInnerA = typename U::Inner;
    

    然后将其用作:

    auto foo = A<B>::mvInnerA<>{};
    

    对于T 类型(让我说)通过U 间接使用仅当mvInnerA 被实例化时,您就不存在上述问题。付出的代价是烦人的&lt;&gt; 的存在以及可以将自定义类型传递给mvInnerA 的事实。

【讨论】:

  • 感谢您的回答。不幸的是,我知道我收到错误的原因(请接受我的道歉,我会修改问题的定义)。但是,我正在寻找解决方法。
  • 哦,好的,从问题看来,您似乎希望它按原样工作。我不明白你是在要求另一种方法。对不起。
  • @user1391279 在我编辑答案之前,像this 这样的解决方案对您有用吗?换句话说,mvInnerA 不再是一个类型,而是一个返回类型为T::inner 的函数。您可以随时使用decltype 获取它,或者使用该函数创建类型的实例。
  • 感谢您的评论。我会接受您的建议作为有效答案。
  • @user1391279 在答案中直接给出了几个替代方案。希望它们对你有用。
【解决方案2】:

如何使用 CRTP 模板参数的内部类型受到严格限制。

在类模板定义本身的范围内是没有用的。实例化模板时,需要完全定义类型 B,就像 skypjack points out 一样,它不是。但是,您可以在没有立即使用类模板实例化的上下文中使用它,这主要是 A 的成员函数。

虽然您不能为 B::Inner 提供类型别名,但您可以拥有类型别名模板

template<class C>
using Inner = typename C::Inner

A 的成员函数可以使用哪些成员函数来避免 typename B::Inner 的冗长,而改用 Inner&lt;B&gt;

【讨论】:

  • 感谢您的最后评论和回答。不幸的是,正如我在 cmets 和问题的定义中已经提到的那样,我知道不可能在 A 中定义成员变量 B::Inner inner 以及为什么不能这样做。但是,我想了解是否有任何可行的解决方法可以让我在A 中使用我尚未考虑过的B::Inner。不幸的是,我不得不承认我未能对问题提供充分的陈述。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-11-30
  • 2018-09-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-31
  • 1970-01-01
相关资源
最近更新 更多