6

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.

Lingxi
  • 14,579
  • 2
  • 37
  • 93

4 Answers4

9

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&).

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • I agree this is simpler for the case where you only want `operator<<` for type `T` defined in its `namespace` or else `to_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. – 5gon12eder Jan 22 '16 at 08:07
  • This is nice. But then I have to overload `operator<<()` for each type that only has overloaded `to_string()`. I want to avoid such tedious work. – Lingxi Jan 22 '16 at 08:23
  • @ling what? Why do you think you have to do that? – Yakk - Adam Nevraumont Jan 22 '16 at 08:55
  • What happens with `struct bob{ templatefriend ostream& operator<<(ostream&, T&& t ); };`? Is there not a risk of ambiguity? – Yakk - Adam Nevraumont Jan 22 '16 at 08:57
  • @Lingxi: No, you don't have to do that for every type. That's why I made the `operator<<` a **template**. It will be instantiated for every type which you print. – MSalters Jan 22 '16 at 09:00
  • @Yakk: Sounds like a fair concern, but I'm not sure what exactly you mean form just that snippet. Considering we're talking ADL, the exact namespaces matter. Also, are you trying to print `bob` objects or others? – MSalters Jan 22 '16 at 09:53
  • There is an ambiguity issue. For example, the overload for `std::basic_string` have `std::basic_ostream` for the first operand. As a result, it won't be more specialized than the custom version. – Lingxi Jan 22 '16 at 10:08
  • 1
    [Here](http://coliru.stacked-crooked.com/a/98a1ec4b4d18c8d3) is a live example of what I mean. – Lingxi Jan 22 '16 at 10:14
  • @Lingxi: Good point. To be precise, that's because there's no `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. – MSalters Jan 22 '16 at 11:35
  • I made a revision to your solution (see [here](http://stackoverflow.com/a/34946808/1348273)). I guess it solves my problem. Thanks for the inspiration. – Lingxi Jan 22 '16 at 12:33
2

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();
}

LIVE

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
1

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.

5gon12eder
  • 24,280
  • 5
  • 45
  • 92
  • Is this SFINAE? So when `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`? – Lingxi Jan 22 '16 at 07:27
  • I think having the `<< to_string(x)` overload if `<< x` does not compile would be nice, if it's possible at all. – Lingxi Jan 22 '16 at 07:34
  • Yes, this is SFINAE. Please see the updated answer (especially in reply to your second comment). I thought about using `std::enable_if` but I couldn't find a straight-forward solution so I went with the admittedly somewhat confusing `std::conditional`. – 5gon12eder Jan 22 '16 at 07:41
  • Changed code to use `std::enable_if_t` and require the result is of type `std::string`. – 5gon12eder Jan 22 '16 at 08:04
1

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;
}
Lingxi
  • 14,579
  • 2
  • 37
  • 93