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