Compilation issue with a custom stream derived from std::ostream

  c++, clang, g++, operator-overloading, ostream

Lately I wrote some code which creates a custom stream derived from std::ostream.
It compiles with clang++ 12.0 while it fails to do so with g++ 10.1.

In both cases, I use the libstdc++ shipped with g++. Moreover, I specify the following parameters to both compilers: -std=c++20 -Wall -Wextra -Wpedantic.

The code below is the minimal reproducible example:

#include <ostream>
#include <vector>

template <typename T> std::ostream& operator<<(std::ostream& s, const std::vector<T>&) 
{
    return s;
}

struct CustomStream : std::ostream {};

template <typename T> CustomStream&& operator<<(CustomStream&& s, const T& v) 
{
    static_cast<std::ostream&>(s) << v;
    return std::move(s);
}

int main() 
{
    CustomStream() << std::vector<int>{};
}

This code, when compiled with clang++, gives no warning at all and does what seems logical to me. Namely it first calls the << operator for CustomStream&&, then calls the << operator for std::ostream&.
On the other hand, g++ gives the following error message which basically enumerates all the failed template deductions:

In file included from /usr/include/c++/10/iostream:39,
                 from main.cpp:1:
/usr/include/c++/10/ostream: In instantiation of ‘struct std::__is_insertable<std::basic_ostream<char>&, const std::vector<int>&, void>’:
/usr/include/c++/10/type_traits:138:12:   required from ‘struct std::__and_<std::__is_convertible_to_basic_ostream<CustomStream>, std::__is_insertable<std::basic_ostream<char, std::char_traits<char> >&, const std::vector<int, std::allocator<int> >&, void> >’
/usr/include/c++/10/type_traits:143:12:   required from ‘struct std::__and_<std::__not_<std::is_lvalue_reference<CustomStream> >, std::__is_convertible_to_basic_ostream<CustomStream>, std::__is_insertable<std::basic_ostream<char, std::char_traits<char> >&, const std::vector<int, std::allocator<int> >&, void> >’
/usr/include/c++/10/ostream:773:5:   required by substitution of ‘template<class _Ostream, class _Tp> typename std::enable_if<std::__and_<std::__not_<std::is_lvalue_reference<_Tp> >, std::__is_convertible_to_basic_ostream<_Ostream>, std::__is_insertable<typename std::__is_convertible_to_basic_ostream<_Tp>::__ostream_type, const _Tp&, void> >::value, typename std::__is_convertible_to_basic_ostream<_Tp>::__ostream_type>::type std::operator<<(_Ostream&&, const _Tp&) [with _Ostream = CustomStream; _Tp = std::vector<int>]’
main.cpp:16:40:   required from here
/usr/include/c++/10/ostream:747:11: error: no match for ‘operator<<’ (operand types are ‘std::basic_ostream<char>’ and ‘const std::vector<int>’)
  746 |       __void_t<decltype(declval<_Ostream&>()
      |                         ~~~~~~~~~~~~~~~~~~~~
  747 |           << declval<const _Tp&>())>>
      |           ^~~~~~~~~~~~~~~~~~~~~~~~
... (stripped)

Thus, as it seems, g++ cannot find the first << operator I defined in the code.
My question is: which compiler is right in this regard? If g++ is right, I have another question: what am I doing wrong?

Any advice will be appreciated.

Note: I use rvalues on purpose. If I replace them with lvalues as follows:

template <typename T> CustomStream& operator<<(CustomStream& s, const T& v) 
{
    static_cast<std::ostream&>(s) << v;
    return s;
}

int main() 
{
    CustomStream s;
    s << std::vector<int>{};
}

then both clang++ and g++ accept the code.

Source: Windows Questions C++

LEAVE A COMMENT