C++ 编译器在封装行为上存在分歧——哪一个做对了?

Posted

技术标签:

【中文标题】C++ 编译器在封装行为上存在分歧——哪一个做对了?【英文标题】:C++ compilers diverge in encapsulation behavior - which one gets it right? 【发布时间】:2018-08-10 18:16:18 【问题描述】:

编译器(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

右侧的表格显示了每个编译器需要公开哪些成员才能接受代码(针对 C++14 编译时)。

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

当需要 f 公开以实例化 A::C&lt;int&gt;D&lt;int&gt; 时,我不同意 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&lt;int&gt;()A::B&lt;0&gt; 的问题很容易回答。 fB 是私有的,并且没有任何其他有趣的依赖项。访问它们应该是格式错误的。 gcc 通常对模板中的访问控制非常宽容,在各种情况下都有一个 metabug )。

A::C&lt;int&gt;的问题比较有趣。这是一个别名模板,但我们实际上在什么上下文中查看别名?它是A(在这种情况下,使C 可访问就足够了)还是在使用它的上下文中(在这种情况下,fB、和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&lt;B&gt; 是在友好类 C 的上下文中完成的,使引用格式正确,还是独立于别名模板特化出现的上下文确定访问?

如果这个问题的答案是访问是独立于上下文确定的,则必须注意确保访问失败仍然被认为是“在函数类型的直接上下文中”(17.9.2 [ temp.deduct] 第 8 段),从而导致扣除失败而不是硬错误。

虽然问题依然悬而未决,但方向似乎是:

CWG 的共识是别名模板的实例化(查找和访问)应该与其他模板一样,在定义上下文中而不是在使用它们的上下文中。不过,它们仍应立即展开。

也就是说,只有C 需要公开,fB 可以保持私密。这就是 ICC 和 MSVC 的解释方式。 Clang 有一个错误,允许别名模板绕过访问 (15914),这就是为什么 clang 要求 f 可访问但不要求 B。但除此之外,clang 似乎在使用点而不是定义点扩展别名。

D&lt;int&gt; 的问题应该完全按照A::C 进行,这里的 CWG 1554 没有问题。 Clang 是唯一一个在 A::CD 之间具有不同行为的编译器,同样是由于错误 15914。


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

【讨论】:

以上是关于C++ 编译器在封装行为上存在分歧——哪一个做对了?的主要内容,如果未能解决你的问题,请参考以下文章

Visual Studio C++ 编译器在局部变量对象上的奇怪行为

Scipy FFT 和 Numpy FFT 在脉冲序列频谱上存在分歧?

C++学习摘要之四:虚函数和多态

《More Effective C++》阅读笔记

网管用了四个月薪资从6K到18K,究竟做对了哪一点?

C++面向对象之封装篇(上)