Template friendship error compilation with GCC but not with clang - c++

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"; }
};

Related

Use of incomplete template types in templates

This is follow up to a question I asked a few weeks ago in which the answer was that it is ill-formed, no diagnostic required, to use a type in a template that is only complete at the time of the template's instantiation but not at the time of its definition.
My follow up question is is this still true in the case in which the incomplete type is itself dependent on the template parameter? because it seems that it is not. The following compiles in all the compilers on Godbolt, even though foo::do_stuff() is using foo_wrapper::value() given only a forward declaration that a class template foo_wrapper will eventually exist.
#include <iostream>
template<typename T>
class foo_wrapper;
template<typename T>
class foo {
foo_wrapper<T>& parent_;
public:
foo(foo_wrapper<T>& wrapped) : parent_(wrapped)
{}
void do_stuff() {
std::cout << "do stuff " << parent_.value() << "\n";
}
};
template<typename T>
class foo_wrapper {
foo<T> foo_;
T value_;
public:
foo_wrapper(T n) :
foo_(*this),
value_(n)
{}
void do_stuff() {
foo_.do_stuff();
}
T value() const {
return value_;
}
};
int main()
{
foo_wrapper<int> fw(42);
fw.do_stuff();
}
This is legal.
The rule of thumb is that everything that depends on template parameters is checked when the template is instantiated. Everything else is either checked when the template is first seen, or when it's instantiated (e.g. MSVC tends to check everything late, and Clang tends to do it as early as possible).

Return type deduction for template in-class friend functions in clang

So up front, what I'm trying to do is not an really intended use case for in-class friend definitions. But it does work under g++ and, best I can tell, is supposed to work according to C++14 specs.
For the purposes of discussion, clang is 5.0.0 and gcc is 7.2.0, although I've tested with other recent and HEAD versions and got identical results. Everything compiled with c++14 flag.
The minimal reproduction of case I'm interested in is bellow.
#include <iostream>
auto foo();
template <typename T>
class Foo
{
friend auto foo()
{
return (T)3.14;
}
};
template <typename T>
void bar(T){}
int main()
{
bar(Foo<float>{});
std::cout << foo() << std::endl;
return 0;
}
Gcc behavior: Compiles and runs. Outputs: 3.14
Clang behavior: Refuses to compile. Error: conflicting types for 'foo'
That in itself seems completely wrong, but since auto doesn't give clang any problems when I take away templatization of Foo, I decided to try and give the foo function a dummy template.
#include <iostream>
template <typename X = int>
auto foo();
template <typename T>
class Foo
{
template <typename X>
friend auto foo()
{
return (T)3.14;
}
};
template <typename T>
void bar(T){}
int main()
{
bar(Foo<float>{});
std::cout << foo() << std::endl;
return 0;
}
Gcc behavior: Compiles and runs. Outputs: 3.14
Clang behavior: Compiles and runs. Outputs: 3
And yes, when compiled with clang, type deduction of foo depends entirely on default template parameter in declaration, despite not appearing anywhere in the definition. o_O
Finally, I decided to try and fix this particular behavior by throwing in a using directive.
#include <iostream>
template <typename X = int>
auto foo();
template <typename T>
class Foo
{
using Type = T;
template <typename X>
friend auto foo()
{
return (Type)3.14;
}
};
template <typename T>
void bar(T){}
int main()
{
bar(Foo<float>{});
std::cout << foo() << std::endl;
return 0;
}
Gcc behavior: Compiles and runs. Outputs: 3.14
Clang behavior: Compiler crashes. Every version I've tried.
The crash is an obvious bug, but I'm more interested in the first two examples. Is this a bug in clang as well? Or is there some subtle reason for why this should be undefined behavior, and I'm just getting lucky with gcc? And more importantly, can anyone think of a workaround that would work on both gcc and clang? The thing I'm trying to do is detect the type with which Foo was instantiated, provided that it is unique, at compile time from outside of Foo.
While I'm still not entirely sure why Clang behaves this way, I have found a workaround that does compile on both Clang and GCC. The later raises a warning, but it can either be suppressed, or slight variation of definitions can be introduced with ifdefs to make the code compile cleanly on both.
#include <iostream>
template <typename T = void>
class A
{
public:
friend auto foo(A<T>);
};
template <typename T>
class Foo
{
friend auto foo(A<void>)
{
return (T)3.14;
}
};
template <typename T>
void bar(T){}
int main()
{
bar(Foo<float>{});
std::cout << foo(A{}) << std::endl;
return 0;
}
This allows deduction of return type of foo() to be based on template parameter with which Foo was instantiated, even if they happen in different blocks of code, which is precisely what I was looking for.

Visibility of inner template class [duplicate]

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.

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.

template template parameters and clang

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.