c++编译器在封装行为上存在分歧——谁做对了?

时间:2022-09-02 09:31:20

Compilers (clang-5.0.0, GCC-7.3, ICC-18 and MSVC-19) diverge w.r.t. accessibility of members of A below.

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

};

Indeed, consider the following usages:

确实,考虑以下的用法:

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
}

The table on the right shows which members each compiler requires to be made public to accept the code (when compiled for C++14).

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

IMHO, ICC and MSVC (ignoring (C) entries) look correct. Except for the first line, GCC seems to be completely ignoring accessibility.

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

I disagree with clang when it requires f to be public to instantiate A::C<int> and D<int>. Like ICC and MSVC, I think C and only C needs to be public. It is true that C uses f but is it not an implementation detail? Notice that C also uses B. If clang were correct, then why does it not require B to be public as well?

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

Finally, let us consider the (C) entries. Msvc requires C to be public when it first encounters the definition of D, that is, MSVC complains about C being private.

最后,让我们考虑(C)项。Msvc要求C在第一次遇到D的定义时公开,也就是说,Msvc抱怨C是私有的。

My questions are:

  1. Am I right (and so is ICC) in my analysis? Otherwise which other the compiler is correct and why?
  2. 我的分析是否正确(ICC也是如此)?否则哪个编译器是正确的,为什么?
  3. Is the MSVC issue yet another incarnation of two-phase instantiation bug in msvc?
  4. MSVC问题是MSVC中另一个两阶段实例化bug的化身吗?

Update: Regarding GCC, this seems to be the bug reported in comment 8, here.

更新:关于GCC,这似乎是注释8中报告的错误。

1 个解决方案

#1


1  

The questions of A::f<int>() and A::B<0> are straightforward to answer. f and B are private, and neither has any other interesting dependencies. Accessing them should be ill-formed. gcc generally is very permissive about access control in templates, there is a metabug outstanding for all sorts of situations (I think all of them are of the form that gcc allows access when it shouldn't, rather than disallowing access when it should).

A:::f ()和A:::B<0>的问题很容易回答。f和B是私有的,它们都没有任何其他有趣的依赖关系。访问它们应该是不恰当的。gcc通常对模板的访问控制非常宽容,在各种情况下都有一个优秀的metabug(我认为它们都是gcc允许访问的形式,而不是在应该访问的时候不允许访问)。

The question of A::C<int> is more interesting. It's an alias template, but in what context do we actually look through the alias? Is it within A (in which case, making C accessible would be sufficient) or is it in the context in which it's used (in which case, f, B, and C all need to be accessible). This question is precisely CWG 1554, which is still active:

A:::C 的问题更有趣。它是一个别名模板,但是在什么上下文中我们实际上要查看别名呢?它是在A(在这种情况下,使C具有可访问性就足够了)还是在使用它的上下文中(在这种情况下,f、B和C都需要具有可访问性)?这个问题正是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:

从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?
};

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?

B::foo对于foo,在befriended类C的上下文中完成,使引用格式良好,或者是独立于别名模板专门化的上下文而确定的访问权限?

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.

如果这个问题的答案是,访问是独立于上下文决定的,必须小心,以确保一个访问失败仍被认为是“直接的函数类型的上下文”(17.9.2[temp.deduct]8段),在扣除失败结果,而不是硬错误。

Although the issue is still open, the direction seems to be:

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

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.

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

Which is to say, only C needs to be made public and f and B can remain private. This is how ICC and MSVC interpret it. Clang has a bug that allows alias templates to circumvent access (15914), which is why clang requires f to be accessible but not B. But otherwise, clang appears to expand the alias at the point of use rather than the point of definition.

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

The question of D<int> should simply follow A::C exactly, there's no issues with CWG 1554 here. Clang is the only compiler to have different behavior between A::C and D, again due to bug 15914.

D 的问题应该简单地遵循A::C,没错,CWG 1554没有问题。Clang是唯一一个在A::C和D之间有不同行为的编译器,同样是由于bug 15914。


To summarize, the question of A::C is an open core language issue, but ICC implements the intended meaning of the language here. The other compilers all have issues with access checking and templates.

综上所述,A::C的问题是一个开放的核心语言问题,但是ICC实现了语言的意图。其他编译器都存在访问检查和模板问题。

#1


1  

The questions of A::f<int>() and A::B<0> are straightforward to answer. f and B are private, and neither has any other interesting dependencies. Accessing them should be ill-formed. gcc generally is very permissive about access control in templates, there is a metabug outstanding for all sorts of situations (I think all of them are of the form that gcc allows access when it shouldn't, rather than disallowing access when it should).

A:::f ()和A:::B<0>的问题很容易回答。f和B是私有的,它们都没有任何其他有趣的依赖关系。访问它们应该是不恰当的。gcc通常对模板的访问控制非常宽容,在各种情况下都有一个优秀的metabug(我认为它们都是gcc允许访问的形式,而不是在应该访问的时候不允许访问)。

The question of A::C<int> is more interesting. It's an alias template, but in what context do we actually look through the alias? Is it within A (in which case, making C accessible would be sufficient) or is it in the context in which it's used (in which case, f, B, and C all need to be accessible). This question is precisely CWG 1554, which is still active:

A:::C 的问题更有趣。它是一个别名模板,但是在什么上下文中我们实际上要查看别名呢?它是在A(在这种情况下,使C具有可访问性就足够了)还是在使用它的上下文中(在这种情况下,f、B和C都需要具有可访问性)?这个问题正是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:

从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?
};

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?

B::foo对于foo,在befriended类C的上下文中完成,使引用格式良好,或者是独立于别名模板专门化的上下文而确定的访问权限?

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.

如果这个问题的答案是,访问是独立于上下文决定的,必须小心,以确保一个访问失败仍被认为是“直接的函数类型的上下文”(17.9.2[temp.deduct]8段),在扣除失败结果,而不是硬错误。

Although the issue is still open, the direction seems to be:

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

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.

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

Which is to say, only C needs to be made public and f and B can remain private. This is how ICC and MSVC interpret it. Clang has a bug that allows alias templates to circumvent access (15914), which is why clang requires f to be accessible but not B. But otherwise, clang appears to expand the alias at the point of use rather than the point of definition.

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

The question of D<int> should simply follow A::C exactly, there's no issues with CWG 1554 here. Clang is the only compiler to have different behavior between A::C and D, again due to bug 15914.

D 的问题应该简单地遵循A::C,没错,CWG 1554没有问题。Clang是唯一一个在A::C和D之间有不同行为的编译器,同样是由于bug 15914。


To summarize, the question of A::C is an open core language issue, but ICC implements the intended meaning of the language here. The other compilers all have issues with access checking and templates.

综上所述,A::C的问题是一个开放的核心语言问题,但是ICC实现了语言的意图。其他编译器都存在访问检查和模板问题。