Reproducible code as below or on godbolt compiles with clang trunk and MSVC but fails with gcc trunk. Given that non-template friend function of a template class could be constrained by requires clause, is it a gcc bug here?
template< typename Derived >
struct base {
friend void foo(Derived const& d) requires requires { bar(d); } {
bar(d);
}
};
namespace adl {
struct S: base<S> {
friend void bar(S const&) {}
};
}
using adl::S;
void test(S const& s) {
foo(s); // gcc error: 'foo' was not declared in this scope
}
Related
I have a template class A<T> and its specialization for integral arguments. And both the class and its specialization declare the method foo(), which I would like to define outside of class bodies:
#include <concepts>
template<class T>
struct A { static void foo(); };
template<std::integral T>
struct A<T> { static void foo(); };
template<class T>
void A<T>::foo() {}
template<std::integral T>
void A<T>::foo() {}
int main() { A<int>::foo(); }
GCC accepts this code.
Clang prints the error https://gcc.godbolt.org/z/hYfYGPfMh :
error: type constraint differs in template redeclaration
template<std::integral T>
And MSVC prints errors on both method definitions:
error C3855: 'A<T>': template parameter 'T' is incompatible with the declaration
error C2447: '{': missing function header (old-style formal list?)
error C2065: 'foo': undeclared identifier
Please suggest how to define methods outside class bodies and make all the compilers happy?
I'm pretty sure that both the MS and Clang compilers are in error here and GCC is compiling your code correctly. Until these bugs are fixed in the other compilers, I suggest to continue using the concept pattern instead of going back to outdated methods. Simply work around the bug using an additional class:
#include <concepts>
#include <iostream>
// This is a work-around for using concept specialization of
// classes in conjunction with out-of-body definition of members.
// Only helpful for MSVC and Clang. GCC is properly compiling
// out-of-body concept specializations.
template <typename T>
class A
{
// For MSVC ONLY: the default template seems require being empty
// for this to work, but do fiddle around with it.
// (Also works with MSVC:)
A() = delete;
A(const A&) = delete;
A(A&&) noexcept = delete;
A& operator =(const A&) = delete;
A& operator =(A&&) noexcept = delete;
~A() = delete;
// Clang and GCC can have members just fine:
// static const char* foo();
};
// We use distinct base classes to define our concept specializations of A.
template <std::signed_integral T>
class A_Signed_Integral
{
public:
static const char* foo();
};
template <std::unsigned_integral T>
class A_Unsigned_Integral
{
public:
static const char* foo();
};
// And then we wrap them using the actual concept specializations of A,
// making the specializations effectivel the same class as the base class.
template <std::signed_integral T>
class A<T> :
public A_Signed_Integral<T>
{
public:
using A_Signed_Integral<T>::A_Signed_Integral; // grab all ctors
using A_Signed_Integral<T>::operator =; // an exceptional case
};
template <std::unsigned_integral T>
class A<T> :
public A_Unsigned_Integral<T>
{
public:
using A_Unsigned_Integral<T>::A_Unsigned_Integral;
using A_Unsigned_Integral<T>::operator =;
};
// Out-of-body definitions can be located to another file
template <std::signed_integral T>
inline const char* A_Signed_Integral<T>::foo()
{
return "using A<std::signed_integral T> foo";
}
template <std::unsigned_integral T>
inline const char* A_Unsigned_Integral<T>::foo()
{
return "using A<std::unsigned_integral T> foo";
}
int main()
{
std::cout << A<signed long>::foo() << std::endl;
std::cout << A<unsigned long>::foo() << std::endl;
return 0;
}
(Tested with all three compilers and works fine: see gcc.godbolt.org)
In the future, once the bugs are fixed it should be relatively easy with search and replace to erase the base classes in favor of just using the concept specializations of A.
EDIT:
Updating the example to work for MSVC, which cannot seem to make use of the default template yet.
I am not a C++ template expert, I tried something like below
template<class T, bool = std::is_integral_v<T>>
struct A
{};
template<class T>
struct A<T, false>
{
static void foo();
};
template<class T>
struct A<T, true>
{
static void foo();
};
template<class T>
void A<T,false>::foo()
{
std::cout << "I am working on a non-integral type" << std::endl;
}
template<class T>
void A<T, true>::foo()
{
std::cout << "I am working on an integral type" << std::endl;
}
int main()
{
A<int>::foo();
A<float> ::foo();
return 0;
}
and the code gave me the results on MS C++ compiler
I am working on an integral type
I am working on a non-integral type
This is the classical 'passkey' pattern which allows a function to be accessible only within the scope of a specific class:
#include <iostream>
template <typename T>
class passkey {
private:
friend T;
passkey() {}
// noncopyable
passkey(const passkey&) = delete;
passkey& operator=(const passkey&) = delete;
};
struct A {
A();
};
void g(int i, passkey<A>) {
std::cout << i;
}
A::A(){g(42,{});}
int main() {
A a;
return 0;
}
Here the funtion g can only be called within A. But is it possible to extend it if A is now a template class? The following snippet does not compile (with clang, but it works with gcc...), because for a template friend, it seems that one must use an elaborated-class-specifier, which seems to lead to a declaration conflict...
#include <iostream>
template <template<typename...> class T>
class passkey {
private:
template<typename...> friend class T;
passkey() {}
// noncopyable
passkey(const passkey&) = delete;
passkey& operator=(const passkey&) = delete;
};
template<typename T>
struct A {
A();
};
void g(int i, passkey<A>) {
std::cout << i;
}
template<typename T>
A<T>::A(){g(42,{});}
int main() {
A<void> a;
return 0;
}
This gives with clang:
>source>:6:38: error: declaration of 'T' shadows template parameter
template<typename...> friend class T;
^
<source>:3:39: note: template parameter is declared here
template <template<typename...> class T>
Is there any trick to have that work?
I am surprised that your variadic template actually compiles with GCC. This is how I would actually write it:
template <template<typename... Ts> class T, typename... Ts>
class passkey {
private:
friend class T<Ts...>;
constexpr passkey() {
return;
}
passkey(passkey const&) = delete;
passkey& operator=(passkey const&) = delete;
};
Furthermore I would let the function g take an r-value reference and call it with std::move explicitly.
Try it here!
When a function body is instantiated, dependent function call overload resolution should find the best match in associated namespace through ADL, otherwise the behavior is undefined, [temp.dep.candidate]§1
If the call would be ill-formed or would find a better match had the lookup within the associated namespaces considered all the function declarations with external linkage introduced in those namespaces in all translation units, not just considering those declarations found in the template definition and template instantiation contexts, then the program has undefined behavior.
For exemple:
struct A {
friend void f(A a){};
};
struct B : A {};
template <class T>
void g (T a) {
f(a);
}
void h(B b){
g(b);//undefined behavior (UB)
}
//TU2.cpp
//...
void f(B){}
I have 2 doubts about which functions could be one of all the function declarations with external linkage introduced in those namespaces
Question 1
Does inline friend function considered to be introduced in the enclosing namespace?
Exemple:
struct A {
friend void f(A a){};
};
struct B : A {};
template <class T>
void g (T a) {
f(a);
}
void h(B b){
g(b);//undefined behavior ??
}
//TU2.cpp
//....
struct C {
operator B();
friend void f(B){}
};
Question 2
Could function template specialization also trigger this undefined behavior? (Potentialy instantiated or instantiated function template specialization)
Exemple:
struct A {
friend void f(A a){};
};
struct B : A {};
template <class T>
void g (T a) {
f(a);
}
void h(B b){
g(b);//undefined behavior ??
}
//TU2.cpp
//...
template <class T> void f(T){}
// optionaly also with:
// template void f(B);
I have the following code which is not compiled with gcc, but compiles perfectly with MSVC.
class B;
class A
{
B *_parent;
public:
template <typename T>
void Do(T val)
{
_parent->DoB(val);
}
A(B *parent)
: _parent(parent)
{
}
};
class B : public A
{
public:
B()
: A(nullptr)
{
}
void DoB(int val)
{
cout << val << endl;
}
};
int main() {
B b;
A a(&b);
a.Do(10);
return 0;
}
Compilation error is as follows:
prog.cpp: In member function ‘void A::Do(T)’:
prog.cpp:15:10: error: invalid use of incomplete type ‘class B’
_parent->DoB(val);
^~
prog.cpp:4:7: note: forward declaration of ‘class B’
class B;
^
According to the similar posts, that behaviour of gcc is correct due to paragraph 14.6.9 of the Standard. But that is counterintuitive since templates should be instantiated only where used. And the only usage occurs after all classes have been defined.
My question is what are the workarounds for this issue? Because using the member template is really convenient in this case. Maybe the problem is with the architecture or something else?
You can declare the parts of A that rely on B to be known, but define them later. The declaration then looks like this
class B;
class A
{
B *_parent;
public:
template <typename T> void Do(T val); /* (see below) */
A(B *parent) : _parent(parent) {}
};
then comes the definition of B
class B : public A { /* As before. */ };
and finally you define the missing pieces
template <typename T> void A::Do(T val)
{
_parent->DoB(val); /* Fine, B is known. */
}
As a side note, the original code compiled with just a warning using gcc 8.
You can add extra layer:
class A
{
B *_parent;
private:
template <typename TB, typename T>
static void CallDoB(TB* b, T&& val) {
b->DoB(std::forward<T>(val));
}
public:
template <typename T>
void Do(T val)
{
CallDoB(_parent, val);
}
A(B *parent) : _parent(parent) {}
};
now b-> is dependent of template and is postponed until real instantiation.
I try to define a static variable outside the class scope like:
template<typename T>
struct Foo {
void set(int i) {
}
static constexpr decltype(&Foo<T>::set) i = &Foo<T>::set;
};
template<typename T>
constexpr decltype(&Foo<T>::set) Foo<T>::i;
Live example.
But I get following error (for all gcc >= 4.7):
conflicting declaration 'constexpr decltype (& Foo<T>::set(int)) Foo<T>::i'
note: previous declaration as 'constexpr decltype (& Foo<T>::set(int)) Foo<T>::i'
All clang version (clang >= 3.2) do not have any problem with my code.
The problem seems to be the function reference. It works without using a template class.
My questions:
Is it a bug?
How to do it in gcc?
I don't know if it's a bug or not, but you can do it like this:
template<typename T>
struct Foo {
void set(int i) {
}
typedef decltype(&Foo<T>::set) function_type;
static constexpr function_type i = &Foo<T>::set;
};
template<typename T>
constexpr typename Foo<T>::function_type Foo<T>::i;
int main()
{
Foo<int> f;
}