Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 w.r.t. 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.

like image 828
Cassio Neri Avatar asked Mar 02 '18 14:03

Cassio Neri


People also ask

What is the concept of encapsulation in C language?

All activities of finance are wrapped under one entity called the “finance department”. This concept of encapsulation is used in C language for data hiding and protection. It can be implemented when the main calling program has an object, the object should be able to find the functions applicable and in the same way, they find the data.

How do you encapsulate variables in C++?

In C++ encapsulation can be implemented using Class and access modifiers. Look at the below program: In the above program the variable x is made private. This variable can be accessed and manipulated only using the functions get () and set () which are present inside the class.

How do you encapsulate data in a class?

As in encapsulation, the data in a class is hidden from other classes, so it is also known as data-hiding. Encapsulation can be achieved by: Declaring all the variables in the class as private and using C# Properties in the class to set and get the values of variables.

How to implement encapsulation in C++ using access specifiers?

As we have seen in above example, access specifiers plays an important role in implementing encapsulation in C++. The process of implementing encapsulation can be sub-divided into two steps: The member function which manipulates the data members should be labeled as public using the public access specifier


1 Answers

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.

like image 187
Barry Avatar answered Oct 17 '22 08:10

Barry