C++ 编译器在封装行为上存在分歧——哪一个做对了?
Posted
技术标签:
【中文标题】C++ 编译器在封装行为上存在分歧——哪一个做对了?【英文标题】:C++ compilers diverge in encapsulation behavior - which one gets it right? 【发布时间】:2018-08-10 18:16:18 【问题描述】:编译器(clang-5.0.0
、GCC-7.3
、ICC-18
和 MSVC-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
右侧的表格显示了每个编译器需要公开哪些成员才能接受代码(针对 C++14 编译时)。
恕我直言,ICC 和 MSVC(忽略 (C)
条目)看起来是正确的。除了第一行,GCC 似乎完全忽略了可访问性。
当需要 f
公开以实例化 A::C<int>
和 D<int>
时,我不同意 clang。像 ICC 和 MSVC 一样,我认为 C
并且只有 C
需要公开。 C
确实使用了f
,但这不是实现细节吗?注意C
也使用B
。如果 clang 是正确的,那为什么它不需要 B
也是公开的呢?
最后,让我们考虑(C)
条目。 MSVC在第一次遇到D
的定义时要求C
是public的,即MSVC抱怨C
是private的。
我的问题是:
-
我的分析是否正确(ICC 也是如此)?否则哪个其他编译器是正确的,为什么?
是 MSVC 问题的另一个体现
two-phase instantiation bug in msvc?
更新:关于 GCC,这似乎是评论 8 here 中报告的错误。
【问题讨论】:
clang 或 icc 和 msvc 在第 3 行是否正确的问题很有趣。此外,很高兴知道 gcc 的怪异之处。您是否在这里提交了有关 gcc 行为的错误报告? @einpoklum 我还没有填写错误报告。在更多的人确认我的期望后,我会这样做。如果事实证明 clang 也是错误的,那么我也会在那里填写错误报告。我不会为 msvc 做,因为(如果我的分析是正确的)他们已经知道了。 您是否在/permissive-
模式下使用MSVC 进行编译?如果没有那个标志,它可能会表现得很奇怪
D
格式不正确,因为无法进行有效的特化(但只有在未实例化时才为 NDR)。
CWG1554 似乎相关。
【参考方案1】:
A::f<int>()
和A::B<0>
的问题很容易回答。 f
和 B
是私有的,并且没有任何其他有趣的依赖项。访问它们应该是格式错误的。 gcc 通常对模板中的访问控制非常宽容,在各种情况下都有一个 metabug )。
A::C<int>
的问题比较有趣。这是一个别名模板,但我们实际上在什么上下文中查看别名?它是在A
(在这种情况下,使C
可访问就足够了)还是在使用它的上下文中(在这种情况下,f
、B
、和C
都需要可访问)。这个问题正好是CWG 1554,还在活跃:
从 17.6.7 [temp.alias] 的当前措辞来看,别名模板和访问控制的交互并不清楚。例如:
template <class T> using foo = typename T::foo; class B typedef int foo; friend struct C; ; struct C foo<B> f; // Well-formed? ;
B::foo
替换foo<B>
是在友好类C
的上下文中完成的,使引用格式正确,还是独立于别名模板特化出现的上下文确定访问?如果这个问题的答案是访问是独立于上下文确定的,则必须注意确保访问失败仍然被认为是“在函数类型的直接上下文中”(17.9.2 [ temp.deduct] 第 8 段),从而导致扣除失败而不是硬错误。
虽然问题依然悬而未决,但方向似乎是:
CWG 的共识是别名模板的实例化(查找和访问)应该与其他模板一样,在定义上下文中而不是在使用它们的上下文中。不过,它们仍应立即展开。
也就是说,只有C
需要公开,f
和B
可以保持私密。这就是 ICC 和 MSVC 的解释方式。 Clang 有一个错误,允许别名模板绕过访问 (15914),这就是为什么 clang 要求 f
可访问但不要求 B
。但除此之外,clang 似乎在使用点而不是定义点扩展别名。
D<int>
的问题应该完全按照A::C
进行,这里的 CWG 1554 没有问题。 Clang 是唯一一个在 A::C
和 D
之间具有不同行为的编译器,同样是由于错误 15914。
总而言之,A::C
的问题是一个开放的核心语言问题,但 ICC 在这里实现了语言的预期含义。其他编译器都存在访问检查和模板的问题。
【讨论】:
以上是关于C++ 编译器在封装行为上存在分歧——哪一个做对了?的主要内容,如果未能解决你的问题,请参考以下文章
Visual Studio C++ 编译器在局部变量对象上的奇怪行为