https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80662

Jonathan Wakely <redi at gcc dot gnu.org> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|jwakely.gcc at gmail dot com       |ville at gcc dot gnu.org

--- Comment #2 from Jonathan Wakely <redi at gcc dot gnu.org> ---
There's no implicit cast, it's because my_stream{} is an rvalue, so uses the
overload for rvalue streams:

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

This returns the base class (as all the standard operator<< overloads do).

The only difference between gcc 6 and 7 is that operator is constrained, as
required by https://wg21.link/lwg2534 and G++ selects the constrained overload
rather than the member my_stream::operator<< (maybe because it's more
specialized, not sure yet). Clang always selects the member function, whether
the other one is constrained or not. EDG incorrectly says there's an ambiguous
overload.

So I don't think this is a libstdc++ bug, the changes to the libstdc++ code are
correct and required for conformance. G++ seems to be choosing the wrong
overload.

Reduced:

namespace std
{
  // <type_traits>
  struct true_type { static constexpr bool value = true; };
  struct false_type { static constexpr bool value = false; };

  template<bool Cond, typename T = void> struct enable_if { using type = T; };
  template<> struct enable_if<false> { };

  template<typename T, typename U> struct is_same : false_type { };
  template<typename T> struct is_same<T, T> : true_type { };

  template<typename T> struct remove_reference { using type = T; };
  template<typename T> struct remove_reference<T&> { using type = T; };
  template<typename T> struct remove_reference<T&&> { using type = T; };

  template<typename T> struct is_lvalue_reference : false_type { };
  template<typename T> struct is_lvalue_reference<T&> : true_type { };

  template<typename T> T declval();

  template<typename...> using void_t = void;

  template<bool C, typename If, typename Else>
    struct conditional
    {
      using type = If;
    };

  template<typename If, typename Else>
    struct conditional<false, If, Else>
    {
      using type = Else;
    };

  template<typename...> struct __and_;

  template<typename T, typename U>
    struct __and_<T, U>
    : conditional<T::value, U, false_type>::type
    { };

  template<typename T, typename U, typename V>
    struct __and_<T, U, V>
    : conditional<T::value, __and_<U, V>, false_type>::type
    { };

  template<typename T>
    struct __not_ : conditional<T::value, false_type, true_type>::type { };

  // <utility>
  template<typename T>
    T&& forward(T& t) { return static_cast<T&&>(t); }

  // <string>
  template<typename C> struct char_traits { };

  struct string
  {
    string(const char* s) : str(s) { }

    const char* str;
  };

  // <ostream>
  template<typename C, typename T = char_traits<C>>
    struct basic_ostream
    {
      basic_ostream& operator<<(const char*)
      {
        return *this;
      }
    };

  using ostream = basic_ostream<char>;

  inline ostream
  operator<<(ostream& os, const string& s)
  {
    os << s.str;
    return os;
  }

#ifndef UNCONSTRAINED
  // GCC 7 code

  template<typename _Tp>
    struct __is_convertible_to_basic_ostream
    {
      template<typename _Ch, typename _Up>
      static basic_ostream<_Ch, _Up>& __check(basic_ostream<_Ch, _Up>*);

      static void __check(...);
    public:
      using ostream_type =
        decltype(__check(declval<typename remove_reference<_Tp>::type*>()));
      constexpr static bool value = !is_same<ostream_type, void>::value;
    };

  template<typename _Ostream, typename _Tp, typename = void>
    struct __is_insertable : false_type {};

  template<typename _Ostream, typename _Tp>
    struct __is_insertable<_Ostream, _Tp,
                           void_t<decltype(declval<_Ostream&>()
                                           << declval<const _Tp&>())>>
                                    : true_type {};

  template<typename _Ostream, typename _Tp>
    inline
    typename enable_if<__and_<__not_<is_lvalue_reference<_Ostream>>,
                               __is_convertible_to_basic_ostream<_Ostream>,
                               __is_insertable<_Ostream&, const _Tp&>>::value,
                       typename __is_convertible_to_basic_ostream<
                         _Ostream>::ostream_type>::type
    operator<<(_Ostream&& __os, const _Tp& __x)
    {
      __os << __x;
      return __os;
    }
#else
  // GCC 6 code

  template<typename _CharT, typename _Traits, typename _Tp>
    inline basic_ostream<_CharT, _Traits>&
    operator<<(basic_ostream<_CharT, _Traits>&& __os, const _Tp& __x)
    {
      __os << __x;
      return __os;
    }
#endif
}

struct my_stream : public std::ostream {
    template<typename T>
    my_stream& operator<<(T&& value)
    {
        std::ostream::operator<<(std::forward<T>(value));
        return *this;
    }
};

int main()
{
    my_stream& s = (my_stream{} << "hello world");
}

Reply via email to