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.