On Sun, 24 Apr 2022 at 20:07, Bo Persson via Std-Discussion <std-discussion@lists.isocpp.org> wrote:
On 2022-04-24 at 11:07, Yongwei Wu via Std-Discussion wrote:
> For code like the following:
>
> template <typename T>
> class Wrapper {
> public:
>      Wrapper(T value) : value_(value) {}
>
>      template <typename CharT, typename Traits>
>      friend std::basic_ostream<CharT, Traits>&
>      operator<<(std::basic_ostream<CharT, Traits>& os,
>                 const Wrapper& obj)
>      {
>          os << obj.value_;
>          return os;
>      }
>
> private:
>      T value_;
> };
>
> Is there a way to move the operator<< definition outside the class template?
>
> I have not found a direction solution, though some workarounds are
> possible: say, proxying through a member function template; or make the
> operator<< template have three template parameters:
>
>      template <typename CharT, typename Traits, typename U>
>      friend std::basic_ostream<CharT, Traits>&
>      operator<<(std::basic_ostream<CharT, Traits>& os,
>                 const Wrapper<U>& obj);

That's not really an assymetry, but the correct way to do it.

Inside that class, Wrapper is a shorthand for Wrapper<T>. Outside the
class there is no shorthand available, so you need to specify its
template parameter explicitly.

And then you have to make the friend declaration match the out-of-class
definition (with an equal number of template parameters).

Arguable somehow.  The three-parameter solution makes operator<< for Wrapper<char> a friend of Wrapper<int>, for example. The inline solution is stricter about who is the friend. Of course, it is relatively trivial.

We can specify correctly if T is the only template parameter for operator<<, say, if we only support std::ostream (but not the wide-character and other character types).

This works:

template <typename T>
class Wrapper;

template <typename T>
std::ostream& operator<<(std::ostream& os, const Wrapper<T>& obj);

template <typename T>
class Wrapper {
public:
    …

    friend std::ostream&
    operator<< <T>(std::ostream& os,
                   const Wrapper& obj);
};


This does not (the compiler will complain about partial specialization):

template <typename T>
class Wrapper;

template <typename CharT, typename Traits, typename T>
std::basic_ostream<CharT, Traits>&
operator<<(std::basic_ostream<CharT, Traits>& os, const Wrapper<T>& obj);

template <typename T>
class Wrapper {
public:
    …

    template <typename CharT, typename Traits>
    friend std::basic_ostream<CharT, Traits>&
    operator<< <CharT, Traits, T>(std::basic_ostream<CharT, Traits>& os,
                                  const Wrapper& obj);
};


--
Yongwei Wu
URL: http://wyw.dcweb.cn/