简体   繁体   中英

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

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

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).

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

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?

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.

My questions are:

  1. Am I right (and so is ICC) in my analysis? Otherwise which other compiler is correct and why?
  2. Is the MSVC issue yet another incarnation of two-phase instantiation bug in msvc ?

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

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).

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:

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.

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.

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.

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.


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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM