Curiously mutually recurring class definitions

梦想的初衷 提交于 2021-02-06 15:01:40

问题


I want type declarations in two classes to mutually depend on each other. Here is a first example that compiles both with clang and gcc:

template <class Sum>
struct A
{
    using X = char;                // (1)
    using Z = typename Sum::B::Y;  // (2)
};

template <class Sum>
struct B
{
    using Y = typename Sum::A::X;
};

struct AplusB
{
    using A = ::A<AplusB>;
    using B = ::B<AplusB>;
};

AplusB::A::Z z;

int main() {}

There is an interesting moment, however. If you swap lines (1) and (2), then it will fail to compile with an error:

error: no type named 'X' in 'A'

That makes me question whether the original code is actually valid in the sense of the C++ standard, or it just so happens to compile?

Here is a second example, which also exploits order of template instantiation:

template <class Sum>
struct A
{
    using X = char;
    using P = typename Sum::B::Q;
};

template <class Sum>
struct B
{
    using Y = typename Sum::A::X;
    using Q = int;
};

struct AplusB
{
    using A = ::A<AplusB>;
    using B = ::B<AplusB>;
};

AplusB::A::X z; // (1)
AplusB::B::Q t; // (2)

int main() {}

Here if you swap (1) and (2) it will fail to compile with error:

error: no type named 'Q' in 'B'

So the question is: Is it actually permitted by standard for class definitions to depend on each other like that?


回答1:


As discussed in another answer, CWG 287's resolution is just the de facto approach followed by implementations, mandating that precisely the entities that precede the "inline" PoI of the member being instantiated are in scope.

Thus, when the lines are swapped, we attempt to access something that hasn't been instantiated yet:

template <class Sum>
struct A
{
    using X = char;               // (#)
    using P = typename Sum::B::Q; // (2.3), (1.2)
};

template <class Sum>
struct B
{
    using Y = typename Sum::A::X; // (2.2), (1.3)
    using Q = int;                // (*)
};

struct AplusB
{
    using A = ::A<AplusB>; // (1.1)
    using B = ::B<AplusB>; // (2.1) 
};

AplusB::B::Q t; // (2)

AplusB::A::X z; // (1) (INDEPENDENT example)

The sequence (2), (2.1), (2.2) and (2.3) is the order in which the instantiations are caused, with the effective PoI preceding that declaration. With (2) declared first, at (2.3), we reference (*), which isn't in scope. If (1) comes first, then at (1.3) we access (#), which is indeed in scope. (In a subsequent declaration of (2), the specialisation of A has already been instantiated fully, so there are no further subtleties.)

If you declare all the "base case" aliases before the "recursive" ones, which was the difference in your first snippet, it works fine either way.



来源:https://stackoverflow.com/questions/48227757/curiously-mutually-recurring-class-definitions

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!