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