Reputation: 1903
I have
a class Value which can be construct with different types (Foo, Bar, int,...).
class Value should have common operations like <<, ==, <,... proceeded at the underlying type
I added the operator << outside class definition.
I have to the following code:
#include <iostream>
struct Foo {
};
struct Bar {
};
struct Value {
template<typename T>
Value(T) {
}
};
std::ostream &operator<<(std::ostream &os, const Bar &) {
return os << "Bar\n";
}
std::ostream &operator<<(std::ostream &os, const Value &value) {
auto visitor = [&](auto a) -> decltype(os << a) {
return os << a;
};
// Works
visitor(Bar{});
// Infinity call of this function with Value.
visitor(Foo{});
return os;
}
int main() {
std::cout << Value(1);
return 0;
}
The problem: If the underlying type does not implements the operator<<, the operator<< from Value gets called recursive infinity. I want to get a compiler error like no match for call operator<<(std:ostream&, const Value&)... to use SFINAE with my visitor pattern (not shown here).
What I need is something like:
[&](auto a) -> std::enable_if_t<addressof?(os << a) != addressof?(os << Value{})>::value> {
return os << a;
};
to disable this lambda, if the functions are the same. Is this possible?
Not valuable solutions:
Upvotes: 3
Views: 179
Reputation: 41100
Without modifying the signature of std::ostream &operator<<(std::ostream &os, const Value &value)
we can check if an attempt to call to operator<<
for the type that a
is deduced to in our lambda is well-formed:
auto visitor = [&](auto a) -> decltype(
static_cast<std::ostream&(*)(std::ostream&, const decltype(a)&)>(&operator<<)
(os, a)
)
{
return os << a;
};
Works with Bar
, fails with Foo
with error message:
error: invalid static_cast from type '<unresolved overloaded function type>' to type 'std::ostream& (*)(std::ostream&, const Foo&
Upvotes: 2
Reputation: 217448
You might add a wrapper to force only one conversion:
template <typename T>
struct OneConversion
{
OneConversion(const T& t) : t(t) {}
operator const T&() const {return t;}
const T& t;
};
template <typename T>
struct isOneConversion : std::false_type {};
template <typename T>
struct isOneConversion<OneConversion<T>> : std::true_type {};
struct Value {
template<typename T, std::enable_if_t<!isOneConversion<T>::value>* = nullptr>
Value(T) {}
};
std::ostream &operator<<(std::ostream &os, const Value &value) {
auto visitor = [&](auto a) -> decltype(os << OneConversion<decltype(a)>(a)) {
return os << OneConversion<decltype(a)>(a);
};
// Works
visitor(Bar{});
visitor(Foo{}); // Error as expected.
return os;
}
Upvotes: 4
Reputation: 96326
You could replace
std::ostream &operator<<(std::ostream &os, const Value &value)
{
// ...
}
with
template <typename T, typename = std::enable_if_t<std::is_same_v<T, Value>>>
std::ostream &operator<<(std::ostream &os, const T &value)
{
// ...
}
You'll still be able to print Value
objects with it, but without implicit conversions.
Putting it into your code makes it fail at visitor(Foo{});
with following error, which seems like what you want.
... main.cpp:29:12: error: no match for call to '(operator<<(std::ostream&, const T&) [with T = Value; <template-parameter-1-2> = void; std::ostream = std::basic_ostream<char>]::<lambda(auto:1)>) (Foo)' visitor(Foo{}); ~~~~~~~^~~~~~~ ...
Upvotes: 2