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

C++ C++;编译器在封装行为上存在分歧——哪一个是正确的?,c++,gcc,clang,private,encapsulation,C++,Gcc,Clang,Private,Encapsulation,编译器(clang-5.0.0,GCC-7.3,ICC-18和MSVC-19)在以下A成员的可访问性方面存在分歧 class A { template <class> static constexpr int f() { return 0; } template <int> struct B {}; template <class T> using C = B<f<T>()>; }; A类{ 模板静态c

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

class A {

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

    template <int> struct B {};

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

};
A类{
模板静态constexpr int f(){return 0;}
模板结构B{};
使用C=B的模板;
};
确实,考虑下面的用法:

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
}
使用D=A::C的
模板;
int main(){
//|铿锵| gcc | icc | msvc
(void)A::f();//1:| f | f | f | f,(C)
(void)A::B{};//2:| B | | B | B,(C)
(void)A::C{};//3:| C,f | C | C
(void)D{};//4:| f | | C | C
}
右边的表格显示了每个编译器需要公开哪些成员才能接受代码(为C++14编译时)

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

我不同意clang要求
f
公开来实例化
A::C
D
。像ICC和MSVC一样,我认为只有
C
需要公开。确实
C
使用了
f
,但这不是一个实现细节吗?请注意,
C
也使用
B
。如果叮当声是正确的,那么为什么不要求
B
也公开呢

最后,让我们考虑<代码>(C)< /代码>条目。当MSVC第一次遇到
D
的定义时,它要求
C
是公开的,也就是说,MSVC抱怨
C
是私有的

我的问题是:
  • 我的分析正确吗(国际商会也是如此)?否则,其他哪个编译器是正确的,为什么
  • MSVC问题是否是另一个 ?

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

    关于
    A::f()
    A::B
    的问题很容易回答
    f
    B
    是私有的,它们都没有任何其他有趣的依赖项。访问它们应该是格式错误的。gcc通常对模板中的访问控制非常宽容,在各种情况下都有一个突出的问题(我认为所有这些问题的形式都是gcc在不应该的时候允许访问,而不是在应该的时候不允许访问)

    A::C
    的问题更有趣。这是一个别名模板,但我们实际上是在什么上下文中查看别名的?它是在
    A
    (在这种情况下,使
    C
    可访问就足够了)还是在使用它的上下文中(在这种情况下,
    f
    B
    C
    都需要可访问)。这个问题恰恰是,它仍然是活跃的:

    从17.6.7[临时别名]的当前措辞来看,别名模板和访问控制之间的相互作用并不清楚。例如:

    template <class T> using foo = typename T::foo;
    
    class B {
      typedef int foo;
      friend struct C;
    };
    
    struct C {
      foo<B> f;    // Well-formed?
    };
    
    使用foo=typename T::foo的模板; B类{ typedef int-foo; 友元结构C; }; 结构C{ foo f;//格式正确吗? }; 用
    B::foo
    替换
    foo
    是在befriended类
    C
    的上下文中完成的,从而使引用格式正确,还是访问权限独立于别名模板专用化出现的上下文而确定

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

    尽管问题仍然悬而未决,但方向似乎是:

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

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

    D
    的问题应该简单地跟在
    A::C
    后面,确切地说,CWG1554在这里没有问题。Clang是唯一一个在
    A::C
    D
    之间有不同行为的编译器,同样是由于bug 15914



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

    关于clang或icc和msvc在第3行是否正确的问题很有趣。此外,了解gcc的怪异也是件好事。你在这里提交了关于gcc行为的错误报告了吗?@einpoklum我还没有填写错误报告。在更多的人确认我的期望后,我会这样做。如果事实证明,叮当声也是错误的,那么我也会在那里填写一份bug报告。我不会为msvc做这件事,因为(如果我的分析正确的话)他们已经知道了。你是在
    /permissive-
    模式下用msvc编译的吗?如果没有标志
    D
    的话,它的行为可能会很奇怪,因为无法进行有效的专门化(但只有在没有实例化的情况下,它才是NDR)。这似乎很相关。