I'm learning SFINAE and this is my first attempt to print "YES" only for those types you can output with std::ostream
(forget about std::operator<<(std::ostream &, T)
for now...):
template <typename T>
void f(const T &) { std::cout << "NO" << std::endl; }
template <typename T, int SFINAE = sizeof(static_cast<std::ostream &(std::ostream::*)(T)>(
&std::ostream::operator<<))>
void f(const T &) { std::cout << "YES" << std::endl; }
Though they seem to work with f(std::vector<int>())
(yielding "NO,") the compiler complains f(0)
is ambiguous: http://ideone.com/VljXFh
prog.cpp:16:5: error: call of overloaded 'f(int)' is ambiguous
f(0);
^
prog.cpp:6:6: note: candidate: void f(const T&) [with T = int]
void f(const T &) { std::cout << "NO" << std::endl; }
^
prog.cpp:10:6: note: candidate: void f(const T&) [with T = int; int SFINAE = 8]
void f(const T &) { std::cout << "YES" << std::endl; }
^
How can I fix my code? Is the "YES" version not more specific than the "NO" version which is completely generic?
All of f(0)
, f(0.)
and f(true)
fail with the same "ambiguous" error. I'm looking for a solution that is applicable to all types accepted by std::ostream::operator<<
. Ideally it shouldn't rely on defining a helper type that "taints" the namespace.
The NO
version is still valid for int
and there is no applicable partial ordering to select between the two overloads, so the call is ambiguous.
One simple way of disambiguating is to add an additional tag argument to the functions:
template <typename T>
void f(const T &, char) { std::cout << "NO" << std::endl; }
// ^^^^
template <typename T, int SFINAE = sizeof(static_cast<std::ostream &(std::ostream::*)(T)>(
&std::ostream::operator<<))>
void f(const T &, int) { std::cout << "YES" << std::endl; }
// ^^^
Now when you call the function, just pass an additional 0
(or write a helper function to do it for you). The SFINAE protected function will be preferred if it is valid because int
is a better match than char
for 0
. See this article for a cleaner way of expressing this disambiguation.
Alternatively, you could write a trait to check if the operator is valid for a given type then use std::enable_if<check<T>>
and std::enable_if<!check<T>>
to avoid the disambiguating argument.
Incidentally, you can use decltype
and trailing return types for this kind of SFINAE and I think it looks a bit cleaner:
template <typename T>
void f(const T &, char) { std::cout << "NO" << std::endl; }
template <typename T>
auto f(const T &t, int) -> decltype(std::declval<std::ostream&>() << t, void())
{ std::cout << "YES" << std::endl; }
When we get C++ Concepts, you'll be able to do something like this (this works in concepts-enabled GCC):
template <typename T>
concept bool Outputtable = requires (T t, std::ostream o) { o << t; };
template <typename T>
void f(const T &) { std::cout << "NO" << std::endl; }
template <Outputtable T>
void f(const T &) { std::cout << "YES" << std::endl; }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With