6

Here is a reproducible example taken from question about using temporary stringstream object:

#include <sstream>                     
#include <string>
#include <iostream>

using namespace std;

std::string transform(std::string);

int main()
{
    int i{};
    cout << transform( static_cast<stringstream &>(stringstream() << i).str() );
}

When trying to compile with clang version 9.0.0 under MacOS High Sierra I got following error:

$ clang++ -std=c++11 x.cc -c
x.cc:12:24: error: non-const lvalue reference to type 'basic_stringstream<...>' cannot bind to a temporary of type 'basic_stringstream<...>'
    cout << transform( static_cast<stringstream &>(stringstream() << i).str() );
                       ^                           ~~~~~~~~~~~~~~~~~~~
1 error generated.

When g++ 9.2.0 is used on same machine (also on Linux) everything compiles fine.

Seems that changing cast from stringstream & to const stringstream & or to stringstream && solves problem.

The question is if this is compiler bug or maybe clang is more strict about some standard rules?

Krzysztof
  • 769
  • 8
  • 27
  • [Cannot reproduce](https://godbolt.org/z/fb_DR2)... – Max Langhof Dec 13 '19 at 15:17
  • FWIW, if you just need a single thing converted, `transform(std::to_string(i))` works and IMHO is easier to understand. – NathanOliver Dec 13 '19 at 15:18
  • 1
    @MaxLanghof IIRC godbolt uses libstdc++ for clang instead of libc++. It reproduces on wandbox: https://wandbox.org/permlink/mwEqfxr8hW7uhx4w – NathanOliver Dec 13 '19 at 15:20
  • @NathanOliver-ReinstateMonica True, with the `stdlib=libc++` it fails: https://godbolt.org/z/4iMPgc. – Max Langhof Dec 13 '19 at 15:22
  • 1
    not the problem but `using namespace std;` + `std::string transform(std::string);` will cause confusion sooner or later – 463035818_is_not_an_ai Dec 13 '19 at 15:28
  • I don't understand why you are even trying to turn a temporary (which may be dropped via RAII) into a lvalue reference. If you really do need lvalue reference you should store into a variable. I don't see any reason for the cast at all in fact. (what does .str() return, a view or a string?) – Paul Stelian Jan 08 '20 at 16:34

2 Answers2

2

Yes, I think that is a bug in libc++.

According to [ostream.rvalue] there is an overload:

template<class charT, class traits, class T>
  basic_ostream<charT, traits>& operator<<(basic_ostream<charT, traits>&& os, const T& x);

But libc++ implements this similar to:

template <class _Stream, class _Tp>
enable_if_t</*...*/, _Stream&&>
operator<<(_Stream&& __os, const _Tp& __x)
{
    // ...
}

The implementation makes this overload a better candidate than the in-class operator<< of ostream in overload resolution if a stream rvalue is used with <<, while the signature in the standard wouldn't be, and it also returns a rvalue reference, when it should return a lvalue reference. It also returns a reference of same type as was passed to it, while it should return a reference to its ostream base class, according to the standard quote.

The rvalue reference can not be bound to a non-const lvalue reference, hence the error.

The bug has already been reported here and there is an open LWG issue regarding the behavior here, which seems to suggest that in the future the standard might be adjusted to mandate libc++'s current behavior.

walnut
  • 21,629
  • 4
  • 23
  • 59
  • So this would be a breaking change for all the code out there implementing the workaround in the question (this happens with fstreams too) ... I wonder if it is possible to return something that can be cast to lvalue reference and rvalue reference so that old code doesn't break – M.M Dec 13 '19 at 21:43
1

The answer to that is a different implementation of ostream insertion between libstdc++ and libc++.

The implementation is almost the same, but libc++ implementation has following code in it (https://github.com/llvm/llvm-project/blob/master/libcxx/include/ostream):

template <class _Stream, class _Tp>
inline _LIBCPP_INLINE_VISIBILITY
typename enable_if
<
    !is_lvalue_reference<_Stream>::value &&
    is_base_of<ios_base, _Stream>::value,
    _Stream&&
>::type
operator<<(_Stream&& __os, const _Tp& __x)
{
    __os << __x;
    return _VSTD::move(__os);
}

This grabs the rvalue strstream you create in situ and returns an rvalue, to which lvalue reference can't bind.

On the other hand, libstdc++ implementation has following:

 template<typename _Ostream>
    using __rvalue_ostream_type =
      typename __is_convertible_to_basic_ostream<
    _Ostream>::__ostream_type;


  template<typename _Ostream, typename _Tp>
    inline
    typename enable_if<__and_<__not_<is_lvalue_reference<_Ostream>>,
                  __is_convertible_to_basic_ostream<_Ostream>,
                  __is_insertable<
                __rvalue_ostream_type<_Ostream>,
                const _Tp&>>::value,
               __rvalue_ostream_type<_Ostream>>::type
    operator<<(_Ostream&& __os, const _Tp& __x)
    {
      __rvalue_ostream_type<_Ostream> __ret_os = __os;
      __ret_os << __x;
      return __ret_os;

Where __rvalue_ostream_type after some magic becomes not an r-value reference. Thus code compiles.

The above is a bug in libstdc++ and can be submitted as a bug report.

SergeyA
  • 61,605
  • 5
  • 78
  • 137
  • It does not seem that there is any point reporting it. It has already been reported and there is an open LWG issue regarding this behavior, see my answer. – walnut Dec 13 '19 at 16:59