Visibility of inner template class [duplicate] - c++

I don't understand why in the following code, I am allowed to create the function print_private_template while the compiler complains about print_private_class:
#include <cstdio>
class A
{
private:
template <unsigned T>
struct B
{
};
struct C
{
};
public:
template <unsigned T>
B<T> getAb()
{
return B<T>();
}
C getAc()
{
return C();
}
};
template<unsigned T>
void print_private_template(const A::B<T> &ab)
{
printf("%d\n", T);
}
void print_private_class(const A::C &ac)
{
printf("something\n");
}
int main(int, char**)
{
A a;
print_private_template(a.getAb<42>());
print_private_class(a.getAc());
return 0;
}
Is this an expected behaviour? a compiler bug/extension?
Just to be clear, my goal is to make the compiler error on both the usage of print_private_template and print_private_class.

Comeau does give an error (when you comment out the print_private_class function and its call in strict C++03 mode.
ComeauTest.c(31): error: class template "A::B" (declared at line 7) is inaccessible
void print_private_template(const A::B &ab)
^
detected during instantiation of "print_private_template" based on
template argument <42U> at line 45
G++ 4.5 on Windows does not report any error with -std=c++ -Wall -pedantic though.
Your class A::C and class template A::B<T> both have the same visibility as any other normal members. Hence, both print_private_class and print_private_template require a diagnostic.
11.8 Nested classes [class.access.nest]
1 A nested class is a member and as such has the same access rights as any other member. The members of
an enclosing class have no special access to members of a nested class; the usual access rules (Clause 11)
shall be obeyed.

As stated by Dirk Gently, GCC doesn't perform access control when instantiating template structs / classes nested in other (template) structs / classes.
One way to work around this is to encapsulate them in a non-template struct:
template<int I> class MyTemplate
{
struct PT
{
template<int, typename = void> struct InnerTemplate;
// ... specialisations here ...
};
public:
typedef typename PT::template InnerTemplate<I>::SomeType SomeType;
};
typedef MyTemplate<1>::PT::InnerTemplate<1> ThisWontWork;
The last line will fail to compile with the error:
error: 'struct MyTemplate<1>::PT' is private within this context
I'll grant that this is ugly, especially having to use PT::template but it seems to effectively prevent clients from instantiating helper templates they aren't meant to access, so it's worth a shot.

It got fixed for GCC 11
Ten years later... and the bug got fixed for GCC 11: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=41437#c13 (another point in the dupe pool had been previously linked to by dirkgently).
A minimal reproduction:
main.cpp
class Out {
protected:
class In {};
};
template <class C>
void f() { Out::In in; }
int main() {
f<Out>();
}
still compiles in GCC 10.2 with all warnings enabled:
g++-10 -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
but fails correctly in clang++ 10:
void f() { Out::In in; }
^
main.cpp:3:11: note: declared protected here
class In {};
^
1 error generated
The above fails to fail in GCC because f is a template function.

Related

Public "using" = decltype(<private>)

In the following (minimized) code, I have a public using declaration that is referring to decltype(something_private): using Foo = decltype(something_private<T>).
On Clang but not GCC this does not compile because of it being private.
Questions:
What is an elegant solution if I do not want to make func<T>() public.
Where in the C++ standard (C++11) does is backup Clang to be correct here?
The below code fails with the following error code on Clang (3.9 - 7.0) but builds on GCC (4.8.4 - 8.2):
class A {
private:
template <class T>
static auto func() -> T; // The actual return type is much
// more complicated, so `using Foo = T` would not work.
public:
template <class T>
using Foo = decltype(func<T>());
};
int main(int, char**) {
A::Foo<int> y;
return y;
}
Clang 7.0 output:
<source>:10:24: error: 'func' is a private member of 'A'
using Foo = decltype(func<T>());
^~~~~~~
<source>:14:7: note: in instantiation of template type alias 'Foo' requested here
A::Foo<int> y;
^
<source>:6:15: note: declared private here
static auto func() -> T;
^
1 error generated.
Compiler returned: 1
https://godbolt.org/z/zD4Hk5
I haven't looked in the standard for citation, but have a workaround for you. Because this works, it makes me think clang just has a bug. When the function is directly in A, it treats the type alias as if it were in the context of the caller, but moving the function into a struct solves that. Meh. I've done a lot of g++ / clang porting lately and while I didn't run into this specifically, it smelled of some things I encountered.
class A {
private:
struct X {
template <class T>
static auto func() -> T;
};
public:
template <class T>
using Foo = decltype(X::func<T>());
};
void bar() {
A::Foo<int> y;
}
https://godbolt.org/z/ozIS-r
UPDATE: added citation.
I think this directly answers your question, and that clang is wrong here.
N4778 (the latest I found), 10.8/p4 (pg 259) ... [Note: Because access control applies to names, if access control is applied to a typedef name, only the accessibility of the typedef name itself is considered. The accessibility of the entity referred to by the typedef is not considered.

Why doesn't the using declaration in a class that derives from a template work as expected in some compilers?

If you derive from a class template, you have to qualify usages of base class members with either Base:: or this->. This is understandable since a member of the base class is a template-dependent name. But there is a third option - you can use a using declaration. I decided to give it a try but with somewhat more convoluted hierarchy:
struct Gadget
{
void gadge()
{
}
};
struct Base1
{
Gadget g;
};
template<typename T>
struct Base2 : Base1
{
};
template<typename T>
struct Widget : Base2<T>
{
using Base1::g;
void foo()
{
g.gadge();
}
};
But the problem is that g.gadge won't compile on some compilers. In particular, GCC 5.5.0 and GCC HEAD (8.0.1) will emit the following error message:
prog.cc: In member function 'void Widget<T>::foo()':
prog.cc:27:11: error: 'struct Base1' has no member named 'gadge'
g.gadge();
^~~~~
On the other hand it compiles and works as expected with GCC 6.1.0, GCC 7.2.0, and all Clang versions that I've tried. Which compiler is right?

Some magic with SFINAE and CRTP using clang++ and g++

Code
The following code gives different output with and without the line marked with * commented:
#include <iostream>
#include <type_traits>
template <bool>
using bool_void_t = void;
template <typename, typename = void>
struct is_complete : std::false_type
{
};
template <typename T>
struct is_complete<T, bool_void_t<sizeof(T) == sizeof(T)>> : std::true_type
{
};
template <typename Derived>
struct Base
{
static constexpr bool value = is_complete<Derived>{};
// using magic = bool_void_t<value>; // *
};
struct Foo : Base<Foo>
{
};
int main()
{
std::cout << std::boolalpha << Foo::value << std::endl;
}
Output
The line marked with * is commented: true.
The line marked with * is not commented: false.
Compiler and its flags
In both cases clang++ 5.0.0 is used as a compiler and the compiler flags are -std=c++17 -Wall -Wextra -Werror -pedantic-errors.
More advanced research
clang++ 5.0.0 (-Wall -Wextra -Werror -pedantic-errors)
-std=c++14 -std=c++17
* is commented false true
* is not commented false false
g++ 7.2.1 (-Wall -Wextra -Werror -pedantic-errors)
-std=c++14 -std=c++17
* is commented true true
* is not commented false false
Questions
Is such behavior of the compilers standard compliant? If yes, what rationale stands behind it?
What differences between C++14 and C++17 could lead to the difference in the observed behavior using the clang++ compiler when the line marked with * is commented (the output is false with the -std=c++14 compiler flag and true with the -std=c++17 one)?
A common problem encountered when using CRTP is that when the base class is instantiated, the derived class is not complete. This means you cannot use member typedefs in the derived class, among other things.
This makes sense when you think about it: a template class is really a way to generate a new class type based on the given template types, so until the compiler reaches the closing } (in an approximate sense), the base class isn't fully defined. If the base class isn't fully defined, then obviously the derived class can't be either.
Because both the base and derived class are empty (in the first example), the compiler considers them complete. I would say this is incorrect, but I'm not an expect and can't be sure. Still, the trick here is that you are instantiating the value of is_complete while defining the base class. After the derived class is fully defined, it will be complete.
Also, for an example of where this matters, consider something like this:
template <typename>
class crtp_traits;
class derived;
template <>
class crtp_traits<derived>
{
public:
using return_type = int;
};
template <typename T>
class base
{
public:
auto get_value() const -> typename crtp_traits<T>::return_type
{
return static_cast<T const*>(this)->do_get_value();
}
};
class derived : public base<derived>
{
public:
auto do_get_value() const -> int
{
return 0;
}
};
The simple solution of giving derived a member typedef using return_type = int; will not work because derived won't be complete by the time base tries to access the typedef.
Turning out my comment into answer:
I think that is_complete make the code ill-formed (No diagnostic required)...
Its value might depends where it is instantiated and breaks ODR.
class A; // A not complete yet
static_assert(!is_complete<A>::value);
class A{}; // Now it is
static_assert(is_complete<A>::value);
From dependent_name
If the meaning of a non-dependent name changes between the definition context and the point of instantiation of a specialization of the template, the program is ill-formed, no diagnostic required. This is possible in the following situations:
a type used in a non-dependent name is incomplete at the point of definition but complete at the point of instantiation.
That seems to be the case for magic.
Even when magic is commented, following should also make code ill formed:
static constexpr bool value = is_complete<Derived>{};

Template friendship error compilation with GCC but not with clang

This code compiles with clang 3.7.1 (with no diagnostic) but fails with GCC 5.3.0 (live example):
#include <iostream>
template<typename T>
struct A {
void foo()
{
static_cast<T*>(this)->implementation();
}
};
struct Crtp : A<Crtp> {
template<typename T>
friend struct A;
private:
void implementation() { std::cout << "implementation()\n"; }
};
int main()
{
Crtp c;
c.foo();
}
GCC's error message is the following:
main.cpp:13:16: error: specialization of 'A' after instantiation
friend struct A;
Which one is right and why? Is it a bug of GCC / clang?
Seems to be an old g++ bug (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52625).
Reported and never corrected, if I understand correctly,
I think this is gcc's bug.
A template friend class declaration is only a declaration, not a definition.
Redeclaration of class template is allowed unless it has different class-key (see N4527 14.5.1.4).
Specialization or instantiation can occur twice or more.
Explicit specialization can occur only once(N4527 14.7.3.6).
Then, gcc's diagnostics is odd because there is no explicit specialization.
We do have some template name resolution odds:
struct Crtp : A<Crtp> {
A x; // A refers to A<Crtp>
};
Now things are clear:
template<typename T> friend struct A;
refers to:
template<typename T> friend struct A<Crtp>;
which is... yeah, partial specialization (really tricky one).
So GCC is correct here.
What do you really need is:
struct Crtp : A<Crtp> {
friend struct A;
private:
void implementation() { std::cout << "implementation()\n"; }
};

How to distingush between template class and implicit template argument inside class defintion? [duplicate]

I have had problems (possibly mine) with template template parameters and clang. The following toy example compiles and runs under g++ 4.7.0, not clang++ 3.0 (based on LLVM 3.0), both ubuntu 12.04.
Toy example (test_1.cpp):
#include <iostream>
#include <memory>
struct AFn
{
void operator()()
{
; // do something
}
};
template<typename T>
struct impl
{
T *backpointer_;
};
template<typename S, template <typename> class T>
struct implT
{
T<S> *backpointer_;
};
template<typename>
class AClass;
template<>
struct implT<AFn, AClass>
{
implT(std::string message) :
message_(message)
{}
void operator()()
{
std::cout << " : " << message_ << std::endl;
}
std::string message_;
};
template<typename Fn>
class AClass
{
private:
std::shared_ptr<implT<Fn, AClass> > p_;
public:
AClass(std::string message) :
p_(std::make_shared<implT<Fn, AClass> >(message))
{}
void call_me()
{
p_->operator()();
}
};
int main(int argc, char **argv)
{
AClass<AFn> *A = new AClass<AFn>("AClass<AFn>");
A->call_me();
delete A;
return 0;
}
clang output:
*****#ely:~$ clang++ -std=c++11 test_1.cpp -o test_1
test_1.cpp:47:30: error: template argument for template template parameter must be a class template or
type alias template
std::shared_ptr<implT<Fn, AClass> > p_;
^
test_1.cpp:47:40: error: C++ requires a type specifier for all declarations
std::shared_ptr<implT<Fn, AClass> > p_;
^~
test_1.cpp:50:36: error: template argument for template template parameter must be a class template or
type alias template
p_(std::make_shared<implT<Fn, AClass> >(message))
^
3 errors generated.
I can't make sense of the first error. It compiles and runs fine with gcc/g++ 4.7.0. Any help would be appreciated.
As noted, it's a Clang bug. AClass there is an injected-class-name, a unique grammatical construct which is both a class-name and a template-name.
Another workaround is to say AClass::template AClass. This avoids needing to qualify AClass with its enclosing namespace.
The same thing happens for me with Clang 3.3.
The solution -- or workaround -- from this SO question is to replace AClass with ::AClass on lines 47 and 50, and then it compiles happily.
To be honest template template parameters make my head hurt. The referenced question suggests it's a Clang bug, but I'm not enough of an expert to be able to say.