Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is it not possible to override operator<< for template classes involving third-party code?

I asked about the following in https://stackoverflow.com/a/51951315/1908650 :

I want to overload template<class T> ostream& operator<<(ostream& os, const optional<unique_ptr<T>>&).

In the comments, @Yakk - Adam Nevraumont notes:

The answer to that question is "you cannot". There is no good legal way to do that for a generic type T; I could explain why, but it would take a new question/answer to do so

I'm creating a new Q. to take up the kind offer...

like image 668
Mohan Avatar asked Aug 21 '18 18:08

Mohan


1 Answers

The proper place to overload operators in in the namespace associated with the type.

For std::optional<std::unique_ptr<T>> there is one associated namespace std that is always there (from ostream and optional and unique_ptr), plus whatever namespace is associated with T. As you want to overload for all types, the namespace(s) associated with T are not useful to you.

It is not legal to introduce a new function or overload into std; in certain limited circumstances you can introduce specializations, but none of them apply here. Adding a new overload of << to std makes your program ill formed, no diagnostic required.

You could (A) use decorated unique_ptr or optionals from your own namespace, or (B) use a decorated ostream, or (C) write a formatter wrapper:

namespace fmt {
  template<class T>
  struct wrapper_t {
    T&& t;
  };
  template<class T>
  struct is_wrapped:std::false_type{};
  template<class T>
  struct is_wrapped<wrapper_t<T>>:std::true_type{};

  template<class OS, class T,
    std::enable_if_t<!is_wrapped<OS&>{}, bool> =true
  >
  auto operator<<( OS& os, wrapper_t<T>&& w )
  -> decltype( os << std::forward<T>(w.t) )
      { return os << std::forward<T>(w.t); }
  template<class OS, class T>
  auto operator<<( wrapper_t<OS&> os, T&& t )
  -> decltype( os.t << std::forward<T>(t) )
      { return os.t << std::forward<T>(t); }

  template<class T>
  wrapper_t<T> wrap(T&& t){ return {std::forward<T>(t)}; }
}

then std::cout << fmt::wrap( foo ) can find overloads of << within fmt, and if none are found invokes << on the contained data.

This also supports fmt::wrap(std::cout) instead of wrapping the arguments. There are probably typos.

like image 120
Yakk - Adam Nevraumont Avatar answered Sep 23 '22 02:09

Yakk - Adam Nevraumont