I recently encountered an interesting C++ code example that compiles with Clang (-std=c++20) but fails with GCC and MSVC. The code involves operator overloading and friend function declarations.
Code Example:
#include <iostream>
template<typename T>
class Foo;
template<typename T>
Foo<T> operator+(int, const Foo<T>&);
template<typename T>
class Foo
{
public:
Foo(const T& val) : data(val) {}
Foo operator+(int);
friend Foo<T> operator+<>(int, const Foo<T>&);
private:
T data;
};
template<typename T>
Foo<T> Foo<T>::operator+(int num)
{
return Foo(this->data + num);
}
template<typename T>
Foo<T> operator+(int num, const Foo<T>& other)
{
return Foo(other.data + num);
}
int main()
{
Foo<int> a(1);
auto v1 = a + 2;
auto v2 = 2 + a;
}
Compiler Behavior: Clang (17.0.1): Works with -std=c++20
GCC (13.2): Fails with error about operator+ not being declared
MSVC (v19.38): Similar failure
GCC has an error:
<source>: In instantiation of 'class Foo<int>':
<source>:35:15: required from here
35 | Foo<int> a(1);
| ^
<source>:15:19: error: template-id 'operator+<>' for 'Foo<int> operator+(int, const Foo<int>&)' does not match any template declaration
15 | friend Foo<T> operator+<>(int, const Foo<T>&);
| ^~~~~~~~~~~
<source>:22:8: note: candidate is: 'Foo<T> Foo<T>::operator+(int) [with T = int]'
22 | Foo<T> Foo<T>::operator+(int num)
| ^~~~~~
<source>: In instantiation of 'Foo<T> operator+(int, const Foo<T>&) [with T = int]':
<source>:37:19: required from here
37 | auto v2 = 2 + a;
| ^
<source>:30:22: error: 'int Foo<int>::data' is private within this context
30 | return Foo(other.data + num);
| ~~~~~~^~~~
<source>:18:7: note: declared private here
18 | T data;
| ^~~~
Workaround: Swapping the order of operator declarations inside the class makes it work:
template<typename T>
class Foo
{
public:
Foo(const T& val) : data(val) {}
friend Foo<T> operator+<>(int, const Foo<T>&);
Foo operator+(int);
private:
T data;
};
template <typename U> friend auto operator+(int, Foo<U> const&) -> Foo<U>;, then the code works with g++ 14.2.0 and--std=c++20.template <typename U> friend ...makes all specializations ofoperator+to be friends, and OP wants to make only one specialization a friend. So it can be done this wayfriend auto ::operator+<>(int, const Foo<T>&) -> Foo<T>;, which is accepted everywhere: gcc.godbolt.org/z/jnd8WWE6j