C++ 编译器在封装行为上存在差异——哪一个是正确的?

C++ compilers diverge in encapsulation behavior - which one gets it right?

编译器(clang-5.0.0GCC-7.3ICC-18MSVC-19)存在分歧 w.r.t。以下 A 成员的可访问性。

class A {

    template <class> static constexpr int f() { return 0; }

    template <int> struct B {};

    template <class T> using C = B<f<T>()>;

};

确实,请考虑以下用法:

template <class T> using D = A::C<T>;

int main() {
                        //    | clang | gcc | icc | msvc
    (void) A::f<int>(); // 1: | f     | f   | f   | f, (C)
    (void) A::B<0>{};   // 2: | B     |     | B   | B, (C)
    (void) A::C<int>{}; // 3: | C,  f |     | C   | C
    (void) D<int>{};    // 4: | f     |     | C   | C
}

右侧的 table 显示每个编译器需要哪些成员 public 来接受代码(为 C++14 编译时)。

恕我直言,ICC 和 MSVC(忽略 (C) 条目)看起来是正确的。除了第一行,GCC 似乎完全忽略了可访问性。

我不同意 clang 要求 f 为 public 来实例化 A::C<int>D<int>。和ICC、MSVC一样,我觉得C也只有C需要public。 C 确实使用了 f 但这不是实现细节吗?请注意 C 也使用 B。如果 clang 是正确的,那么为什么它不要求 B 也为 public?

最后,让我们考虑 (C) 个条目。 MSVC在第一次遇到D的定义时要求C为public,也就是MSVC抱怨C是私有的

我的问题是:

  1. 我的分析是否正确(ICC 也是如此)?否则哪个其他编译器是正确的,为什么?
  2. MSVC 问题是 two-phase instantiation bug in msvc?

更新:关于GCC,这似乎是评论8中报告的错误,here

A::f<int>()A::B<0>的问题很容易回答。 fB 是私有的,并且没有任何其他有趣的依赖项。访问它们应该是 ill-formed。 gcc 通常对模板中的访问控制非常宽松,在各种情况下都有一个 metabug 突出(我认为所有这些都是 gcc 在不应该访问时允许访问的形式,而不是在不允许访问时禁止访问这应该)。

A::C<int>的问题比较有意思。这是一个别名模板,但我们实际上是在什么上下文中查看别名的呢?它是 within A(在这种情况下,使 C 可访问就足够了)还是在使用它的上下文中(在这种情况下,fBC 都需要可访问)。本题恰恰是CWG 1554,目前还处于活动状态:

The interaction of alias templates and access control is not clear from the current wording of 17.6.7 [temp.alias]. For example:

template <class T> using foo = typename T::foo;

class B {
  typedef int foo;
  friend struct C;
};

struct C {
  foo<B> f;    // Well-formed?
};

Is the substitution of B::foo for foo<B> done in the context of the befriended class C, making the reference well-formed, or is the access determined independently of the context in which the alias template specialization appears?

If the answer to this question is that the access is determined independently from the context, care must be taken to ensure that an access failure is still considered to be “in the immediate context of the function type” (17.9.2 [temp.deduct] paragraph 8) so that it results in a deduction failure rather than a hard error.

虽然问题还未解决,但方向似乎是:

The consensus of CWG was that instantiation (lookup and access) for alias templates should be as for other templates, in the definition context rather than in the context where they are used. They should still be expanded immediately, however.

也就是说只需要C设为public,fB就可以保持私有。 ICC 和 MSVC 就是这样解释它的。 Clang 有一个允许别名模板规避访问的错误 (15914),这就是为什么 clang 需要 f 而不是 B 才能访问的原因。但除此之外,clang 似乎在使用点而不是定义点扩展了别名。

D<int> 的问题应该完全遵循 A::C,这里的 CWG 1554 没有问题。 Clang 是唯一在 A::CD 之间具有不同行为的编译器,也是由于错误 15914。


总而言之,A::C的问题是一个开放核心语言问题,但ICC在这里实现了语言的预期含义。其他编译器都存在访问检查和模板问题。