I've seen types that have corresponding to_string()
function, but haven't overloaded operator<<()
. So, when inserting to stream, one has to << to_string(x)
which is verbose. I'm wondering whether it's possible to write a generic function that users operator<<()
if supported and falls back to << to_string()
if not.
SFINAE is overkill, use ADL.
The trick is to make sure that an operator<<
is available, not necessarily the one supplied by the type definition:
namespace helper {
template<typename T> std::ostream& operator<<(std::ostream& os, T const& t)
{
return os << to_string(t);
}
}
using helper::operator<<;
std::cout << myFoo;
This trick is commonly used in generic code which needs to choose between std::swap<T>
and a specialized Foo::swap(Foo::Bar&, Foo::Bar&)
.
operator<<()
for each type that only has overloaded to_string()
. I want to avoid such tedious work. –
Sinatra struct bob{ template<class T>friend ostream& operator<<(ostream&, T&& t ); };
? Is there not a risk of ambiguity? –
Mitchmitchael operator<<
a template. It will be instantiated for every type which you print. –
Misvalue bob
objects or others? –
Misvalue std::basic_string
have std::basic_ostream
for the first operand. As a result, it won't be more specialized than the custom version. –
Sinatra operator<<
for just std::string
. The ambiguity is between two templates, and the idea of my solution is that a template candidate ranks worse than a non-template candidate. There's probably a hack around that, worsening overload candidates is a known technique. –
Misvalue Try
template <typename T>
void print_out(T t) {
print_out_impl(std::cout, t, 0);
}
template <typename OS, typename T>
void print_out_impl(OS& o, T t,
typename std::decay<decltype(
std::declval<OS&>() << std::declval<T>()
)>::type*) {
o << t;
}
template <typename OS, typename T>
void print_out_impl(OS& o, T t, ...) {
o << t.to_string();
}
Yes, it is possible.
#include <iostream>
#include <sstream>
#include <string>
#include <type_traits>
struct streamy
{
};
std::ostream&
operator<<(std::ostream& os, const streamy& obj)
{
return os << "streamy [" << static_cast<const void *>(&obj) << "]";
}
struct stringy
{
};
std::string
to_string(const stringy& obj)
{
auto oss = std::ostringstream {};
oss << "stringy [" << static_cast<const void *>(&obj) << "]";
return oss.str();
}
template <typename T>
std::enable_if_t
<
std::is_same
<
std::string,
decltype(to_string(std::declval<const T&>()))
>::value,
std::ostream
>&
operator<<(std::ostream& os, const T& obj)
{
return os << to_string(obj);
}
int
main()
{
std::cout << streamy {} << '\n';
std::cout << stringy {} << '\n';
}
The generic operator<<
will only be available if the expression to_string(obj)
is well-typed for obj
a const T&
and has a result of type std::string
. As you have already conjectured in your comment, this is indeed SFINAE at work. If the decltype
expression is not well-formed, we will get a substitution failure and the overload will disappear.
However, this will likely get you into troubles with ambiguous overloads. At the very least, put your fallback operator<<
into its own namespace
and only drag it in locally via a using
declaration when needed. I think you will be better off writing a named function that does the same thing.
namespace detail
{
enum class out_methods { directly, to_string, member_str, not_at_all };
template <out_methods> struct tag {};
template <typename T>
void
out(std::ostream& os, const T& arg, const tag<out_methods::directly>)
{
os << arg;
}
template <typename T>
void
out(std::ostream& os, const T& arg, const tag<out_methods::to_string>)
{
os << to_string(arg);
}
template <typename T>
void
out(std::ostream& os, const T& arg, const tag<out_methods::member_str>)
{
os << arg.str();
}
template <typename T>
void
out(std::ostream&, const T&, const tag<out_methods::not_at_all>)
{
// This function will never be called but we provide it anyway such that
// we get better error messages.
throw std::logic_error {};
}
template <typename T, typename = void>
struct can_directly : std::false_type {};
template <typename T>
struct can_directly
<
T,
decltype((void) (std::declval<std::ostream&>() << std::declval<const T&>()))
> : std::true_type {};
template <typename T, typename = void>
struct can_to_string : std::false_type {};
template <typename T>
struct can_to_string
<
T,
decltype((void) (std::declval<std::ostream&>() << to_string(std::declval<const T&>())))
> : std::true_type {};
template <typename T, typename = void>
struct can_member_str : std::false_type {};
template <typename T>
struct can_member_str
<
T,
decltype((void) (std::declval<std::ostream&>() << std::declval<const T&>().str()))
> : std::true_type {};
template <typename T>
constexpr out_methods
decide_how() noexcept
{
if (can_directly<T>::value)
return out_methods::directly;
else if (can_to_string<T>::value)
return out_methods::to_string;
else if (can_member_str<T>::value)
return out_methods::member_str;
else
return out_methods::not_at_all;
}
template <typename T>
void
out(std::ostream& os, const T& arg)
{
constexpr auto how = decide_how<T>();
static_assert(how != out_methods::not_at_all, "cannot format type");
out(os, arg, tag<how> {});
}
}
template <typename... Ts>
void
out(std::ostream& os, const Ts&... args)
{
const int dummy[] = {0, ((void) detail::out(os, args), 0)...};
(void) dummy;
}
Then use it like so.
int
main()
{
std::ostringstream nl {"\n"}; // has `str` member
out(std::cout, streamy {}, nl, stringy {}, '\n');
}
The function decide_how
gives you full flexibility in deciding how to output a given type, even if there are multiple options available. It is also easy to extend. For example, some types have a str
member function instead of an ADL find-able to_string
free function. (Actually, I already did that.)
The function detail::out
uses tag dispatching to select the appropriate output method.
The can_HOW
predicates are implemented using the void_t
trick which I find very elegant.
The variadic out
function uses the “for each argument” trick, which I find even more elegant.
Note that the code is C++14 and will require an up-to-date compiler.
to_string(x)
compiles, the return os << to_string(obj);
overload exists and not otherwise? Could I use std::enable_if
instead of std::conditional
? –
Sinatra << to_string(x)
overload if << x
does not compile would be nice, if it's possible at all. –
Sinatra std::enable_if
but I couldn't find a straight-forward solution so I went with the admittedly somewhat confusing std::conditional
. –
Humanist std::enable_if_t
and require the result is of type std::string
. –
Humanist Based on the answer of @MSalters (credits goes to him), this one solves my problem and should make a complete answer.
#include <iostream>
#include <string>
#include <type_traits>
struct foo_t {};
std::string to_string(foo_t) {
return "foo_t";
}
template <class CharT, class Traits, class T>
typename std::enable_if<std::is_same<CharT, char>::value,
std::basic_ostream<CharT, Traits>&>::type
operator<<(std::basic_ostream<CharT, Traits>& os, const T& x) {
return os << to_string(x);
}
int main() {
std::cout << std::string{"123"} << std::endl;
std::cout << foo_t{} << std::endl;
}
© 2022 - 2024 — McMap. All rights reserved.
operator<<
for typeT
defined in itsnamespace
or elseto_string(T)
and nothing more, which, admittedly, is what the OP has asked for, so +1. If you need to dispatch further, this won't work. Also, the error messages generated by this solution might not be as helpful as they could be. – Humanist