I came across some odd thing I would like to have an explanation for. The following code snippet provides a simple class template type
and two operator<<
s: one for specializations of type
and one for a std::pair
of type
specializations.
#include <ostream>
#include <utility>
template <typename T>
class type {
public:
T value_;
};
template <typename CTy, typename CTr, typename T>
std::basic_ostream<CTy,CTr>&
operator<<(std::basic_ostream<CTy,CTr>& os, type<T> const& a)
{
return os << a.value_;
}
template <typename CTy, typename CTr, typename T>
std::basic_ostream<CTy,CTr>&
operator<<(std::basic_ostream<CTy,CTr>& os, std::pair<T const, T const> const& a)
{
return os << a.first << ',' << a.second;
}
#include <iostream>
int
main()
{
using float_type = type<float>;
float_type const a = { 3.14159 };
float_type const b = { 2.71828 };
#if 0
std::cout << std::make_pair(a, b)
<< std::endl;
#else
std::cout << std::pair<float_type const, float_type const>(a, b)
<< std::endl;
#endif
}
The main
function provides a specialization and two variables of that specialization. There are two variants for displaying the variables as a std::pair
. The first fails because std::make_pair
seems to strip the const
specifier from the variables, which in turn doesn't match with the signature of the second operator<<
: std::pair<T const, T const>
. However, constructing a std::pair
specialization (second std::cout
line in main
) works as well as removing the const
specification for T
from operator<<
for std::pair
, i.e. std::pair<T, T>
.
Compiler messages::
gcc 4.9.2
std_make_pair.cpp: In function 'int main()':
std_make_pair.cpp:52:35: error: cannot bind 'std::ostream {aka std::basic_ostream<char>}' lvalue to 'std::basic_ostream<char>&&'
std::cout << std::make_pair(a, b) << std::endl;
^
In file included from std_make_pair.cpp:3:0:
/usr/include/c++/4.9.2/ostream:602:5: note: initializing argument 1 of 'std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&&, const _Tp&) [with _CharT = char; _Traits = std::char_traits<char>; _Tp = std::pair<type<float>, type<float> >]'
operator<<(basic_ostream<_CharT, _Traits>&& __os, const _Tp& __x)
^
clang 3.5 (non-viable functions from system headers removed)
std_make_pair.cpp:52:13: error: invalid operands to binary expression ('ostream' (aka 'basic_ostream<char>')
and 'pair<typename __decay_and_strip<const type<float> &>::__type, typename __decay_and_strip<const
type<float> &>::__type>')
std::cout << std::make_pair(a, b) << std::endl;
~~~~~~~~~ ^ ~~~~~~~~~~~~~~~~~~~~
std_make_pair.cpp:30:1: note: candidate template ignored: can't deduce a type for 'T' which would make
'const T' equal 'type<float>'
operator<<(std::basic_ostream<CTy,CTr>& os, std::pair<T const, T const> const& a)
so, here's the question: am I supposed to specify an operator<<
taking a std::pair
of T
instead of T const
? Isn't that watering down the contract I'm setting up with any user of the functionality, i.e. with T const
I basically promise to use T
only in non-mutating ways?
The first fails because
std::make_pair
seems to strip the const specifier from the variables, which in turn doesn't match with the signature of the secondoperator<<: std::pair<T const, T const>
That is correct. make_pair
is a function template that relies on std::decay
to explicitly drop const
, volatile
, and &
qualifiers:
template <class T1, class T2> constexpr pair<V1, V2> make_pair(T1&& x, T2&& y);
Returns:
pair<V1, V2>(std::forward<T1>(x), std::forward<T2>(y));
whereV1
andV2
are determined as follows: LetUi
bedecay_t<Ti>
for eachTi
. Then eachVi
isX&
ifUi
equalsreference_wrapper<X>
, otherwiseVi
isUi
.
The compiler is completely correct to reject your code - you added a stream operator for pair<const T, const T>
, but are trying to stream a pair<T, T>
. The solution is to just remove the extra const
requirement in your stream operator. Nothing in that function requires that the pair
consist of const
types - just that the types themselves are streamable, which is independent of their const
ness. There is nothing wrong with this:
template <typename CTy, typename CTr, typename T>
std::basic_ostream<CTy,CTr>&
operator<<(std::basic_ostream<CTy,CTr>& os, std::pair<T, T> const& a)
{
return os << a.first << ',' << a.second;
}
You're already taking the pair
by reference-to-const, it's not like you can modify its contents anyway.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With