I have an overloaded <<
for an aliased std::variant
(A::Var
). I also have a templated function defined in a class in a different namespace C::Wrapper
which just forwards its argument to an std::ostream
.
I am trying to invoke it from another function defined within a class in A
, A::Foo
, but this is giving me compiler errors. This toy example is below.
#include <iostream>
#include <variant>
namespace C {
struct Wrapper {
template<typename T>
auto& operator<<(T&& v) {
std::cout << std::forward<T>(v);
return *this;
}
};
}
namespace A {
using Var = std::variant<bool, int>;
auto& operator<<(std::ostream& os, const A::Var& v) {
std::visit([&os](auto i) { os << i; }, v);
return os;
}
struct Foo {
void m() {
C::Wrapper wrap;
Var v{3};
wrap << "hi"; // works
wrap << v; // compiler error
}
};
}
int main() {
A::Foo a;
a.m();
}
g++ -std=c++17
gives the following error:
main.cpp: In instantiation of ‘auto& C::Wrapper::operator<<(T&&) [with T = std::variant<bool, int>&]’:
main.cpp:27:11: required from here
main.cpp:8:15: error: no match for ‘operator<<’ (operand types are ‘std::ostream’ {aka ‘std::basic_ostream<char>’} and ‘std::variant<bool, int>’)
8 | std::cout << std::forward<T>(v);
... many candidate functions none including my overloaded << for A::Var
I expected it to compile successfully and print 3
when run. I tried making all the definitions out of line, removing the const
qualifier, making the overloaded <<
global, but none of those worked.
How can I fix this error, while keeping the namespace and class structure?
UPDATE:
The code compiles with gcc by defining <<
in the global namespace, like this, but it fails with clang:
namespace A {
using Var = std::variant<bool, int>;
}
auto &operator<<(std::ostream &os, const A::Var &v) {
std::visit([&os](auto i) { os << i; }, v);
return os;
}
namespace A {
struct Foo {
void m() {
C::Wrapper wrap;
A::Var v{3};
wrap << "hi"; // works
wrap << v; // compiler error
}
};
} // namespace A
Error with clang++ -std=c++17
:
main.cpp:7:15: error: call to function 'operator<<' that is neither visible in the template definition nor found by argument-dependent lookup
std::cout << std::forward<T>(v);
^
main.cpp:28:10: note: in instantiation of function template specialization 'C::Wrapper::operator<<<std::variant<bool, int> &>' requested here
wrap << v; // compiler error
^
main.cpp:17:7: note: 'operator<<' should be declared prior to the call site
auto &operator<<(std::ostream &os, const A::Var &v) {
^
1 error generated.
Why does it work with gcc, but not with clang?
Argument-dependent lookup (ADL) doesn't consider namespaces associated with aliases. (That wouldn't make sense either. Should the compiler remember all namespaces having an alias to std::variant<bool, int>
and then suddenly consider all functions declared there as belonging to std::variant<bool, int>
?)
Make Var
a proper independent type. Either you would make it a class with a private std::variant<bool, int>
member and then you can implement and forward the parts of the interface of std::variant
that you need, or you let Var
inherit publicly from std::variant<bool, int>
and inherit the constructors, which has the benefit that std::visit
will still work out-of-the-box.
Then ADL will take into account operator<<
overloads in the namespace A
when you apply <<
to a Var
.