Mohan
Mohan

Reputation: 8843

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

Upvotes: 6

Views: 232

Answers (2)

Chris Uzdavinis
Chris Uzdavinis

Reputation: 6131

In addition to what has been said, there's another issue involving ODR problems. If you overload a function for types you don't control, it is conceivable that someone else also overloads it differently. Then you compile your code with your overload, they compile their code with their overload, and when you work for the same company you end up with multiple versions of the same-signature function in different translation units. Again, undefined behavior, no diagnostic required.

Sanitizers may find this, and perhaps it sounds contrived, but such things happen from time to time.

Upvotes: 0

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275340

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.

Upvotes: 8

Related Questions