I am trying to learn how to use SFINAE.
For practice purposes, I was trying to make an std::ostream
wrapper in order to make a custom formatter.
Here is my SFINAE and custom output class.
// Tester
template <class O>
struct is_ostreamable {
template <class T>
static auto check(T t) -> decltype(std::declval<std::ostream &>() << t, std::true_type());
template <class>
static auto check(...) -> std::false_type;
public:
static constexpr bool value{std::is_same_v<decltype(check<O>(0)), std::true_type>};
};
// Custom class
struct CustomOutput {
// Constructor etc...
CustomOutput(std::ostream &os = std::cout) : os{os} {}
std::ostream &os;
// Problematic template function
template <class O, class = std::enable_if_t<is_ostreamable<O>::value>>
CustomOutput &operator<<(O o) {
os << o;
return *this;
}
};
It words perfectly to not enable the template for struct
or class
that cannot be printed via operator<<
.
However, with this SFINAE, ostream manipulators are not working... And I can't figure out why.
The error, and my expectations:
int main(void){
CustomOutput{} << "hi"; // Fine
std::vector<int> vec;
// CustomOutput{} << vec; // Error. Expected
CustomOutput{} << std::endl; // Error. WHY?
}
Maybe I missed something? Any help would be greatly appreciated.
First, fix your ostreamable
class. Currently, your class requires T
to be copy-constructible from 0
. This is not the case for many classes. It should use std::declval
to create the value instead:
template <class O>
struct is_ostreamable {
template <class T>
static auto check(int) -> decltype(std::declval<std::ostream &>() << std::declval<T>(), std::true_type());
template <class>
static auto check(...) -> std::false_type;
public:
static constexpr bool value{std::is_same_v<decltype(check<O>(0)), std::true_type>};
};
Two changes are made here:
The operand to decltype
uses std::declval<T>()
to create the object of type T
. std::declval<T>
is an (intentionally undefined) function template that generates an object of type T
when used in an unevaluated operand (such as that to decltype
, or sizeof
, noexcept
operator, etc.) without dependence on a specific construction signature (copy-construction from 0
in your case).
The parameter to check
is replaced with int
. The initializer of the value
variable calls check
with the argument 0
, so this int
parameter ensures that (int)
ranks higher than (...)
in overload resolution, so that the true_type
overload gets chosen when possible.
You need to provide a special overload for function-style manipulators (std::endl
, std::flush
, etc.):
using manip = std::ostream& (*)(std::ostream&);
CustomOutput& operator<<(manip m) {
os << m;
return *this;
}
There is, unfortunately, no way to make the generic template version support this feature. This is because std::endl
is a function template:
template <class CharT, class Traits>
std::basic_ostream<CharT, Traits>& endl(td::basic_ostream<CharT, Traits>& os);
For a function template to be used, the appropriate template arguments have to be determined. It is not possible to deduce the type-template parameter T
as a generic template.
Anyway, this is probably the only special overload you are going to need.
I know there is already an accepted answer but I would like to mention a bit prettier C++20 way of doing the same thing by using concepts:
#include <iostream>
#include <concepts>
using OManipulator= std::ostream&(&)(std::ostream &);
template <typename T>
concept OStreamable = requires(T t) {
std::declval<std::ostream&>() << t;
};
struct CustomOutput {
std::ostream &os;
CustomOutput(std::ostream &os = std::cout)
: os{os}
{}
template <typename T> requires OStreamable<T>
CustomOutput& operator<<(T out) {
os << out;
return *this;
}
CustomOutput& operator<<(OManipulator out) {
os << out;
return *this;
}
};
int main(void){
CustomOutput{} << "hello";
CustomOutput{} << std::endl;
CustomOutput{} << "world";
}
Basically, in C++20, problem with manipulators needs to be solved in the same way as pre-C++20, by providing a special overload for them.
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