Curious results from decltype( std::devlcal<std::ostream>() << std::declval<T>() ) when using AppleClang
Asked Answered
W

1

6

Question

Consider the following struct:

template<typename T>
struct stream
{
  using type = decltype(
      std::declval<std::ostream>() << std::declval<T>()
    );
};

template<typename T>
using stream_t = typename stream<T>::type;

The "value" of stream_t<T> when using certain built-in types (int, float, ...) for T is std::ostream&, as I expected.

But when using std::string, char, int*, or some streamable dummy struct for T, the type is an rvalue reference, std::ostream&&.

Once std::declval<std::ostream>() (returns an std::ostream&&) is replaced withstd::declval<std::ostream&> (returns an std::ostream&, due to reference collapsing rule, right?) the returned type is the expected std::ostream&. Is there some rvalue overload of operator<< that I don't know about?

Why is this happening?

Compiler specs

The results above are obtained with AppleClang 11.0.0.11000033. When using gcc-7.4 instead, the result is always std::ostream&, as expected.

Complete source

#include <iostream>
#include <type_traits>

/* ************************************
 * Sans reference
 * ************************************ */

template<typename T>
struct stream
{
  using type = decltype(
      std::declval<std::ostream>() << std::declval<T>()
    );
};

template<typename T>
using stream_t = typename stream<T>::type;

/* ************************************
 * With reference
 * ************************************ */

template<typename T>
struct stream_ref
{
  using type = decltype(
      std::declval<std::ostream&>() << std::declval<T>()
    );
};

template<typename T>
using stream_ref_t = typename stream_ref<T>::type;

/* ************************************
 * Dummy struct
 * ************************************ */

struct Dummy 
{
  friend std::ostream& operator<<(std::ostream&, const Dummy&);
};

/* ************************************
 * Static asserts
 * ************************************ */

static_assert( std::is_same_v<stream_t<int>,   std::ostream&> );
static_assert( std::is_same_v<stream_t<float>, std::ostream&> );

static_assert( std::is_same_v<stream_t<std::string>, std::ostream&&> );
static_assert( std::is_same_v<stream_t<const char*>, std::ostream&&> );
static_assert( std::is_same_v<stream_t<int*>,        std::ostream&&> );
static_assert( std::is_same_v<stream_t<Dummy>,       std::ostream&&> );

static_assert( std::is_same_v<stream_ref_t<std::string>, std::ostream&> );
static_assert( std::is_same_v<stream_ref_t<const char*>, std::ostream&> );
static_assert( std::is_same_v<stream_ref_t<int*>,        std::ostream&> );
static_assert( std::is_same_v<stream_ref_t<Dummy>,       std::ostream&> );

int main(int argc, char** argv)
{
  return 0;
}
Wolters answered 9/12, 2019 at 10:46 Comment(10)
I can't reproduce: godbolt.org/z/QCzJN2 . Can you provide a more concrete example?Whimwham
Also provide the compiler where this is happening. Seems pretty weird. I would ask you to try this on multiple compilers.Engleman
@parktomatomi: T = int* is supposed to yield std::ostream&&, your code asserts equality to std::ostream&. Nevertheless, the static assert with ` std::ostream&&` fails as well.Wolters
@Tanveer Badar: I've added the compiler info. I'll try it on another compiler soon.Wolters
@Tanveer Badar: I've tried with gcc-7.4. Here, the result is always std::ostream&, i.e. the middle block of static_asserts fail, all others pass.Wolters
@Wolters Thanks for cleaning up the example. Interestingly, even though you're on apple, I can only reproduce this on MSVC: godbolt.org/z/d5axNC . clang on Linux still behaves correctly. Weird!Whimwham
Nothing fishy in the libcxx (clang) source: opensource.apple.com/source/clang/clang-800.0.38/src/projects/…Whimwham
Have you tried to compare with std::invoke_result_t(operator<< <std::ostream, T>, declval<std::ostream>(), declval<T>())? (I am on mobile otherwise I would do it myself)Spikes
libc++ implements the PR of LWG1203.Katinakatine
@Katinakatine Thank you! Is there a documentation of all issues resolved in libc++? So far, I've only found this, etc... and none of them seem to go as far back as LWG1023.Wolters
O
2

Actually this behavior is not Apple Clang specific, but common for all modern C++ compilers including GCC, Clang, MSVC, which all accept your program. Demo: https://gcc.godbolt.org/z/8ex6Pc9nb

These checks

static_assert( std::is_same_v<stream_t<std::string>, std::ostream&&> );
static_assert( std::is_same_v<stream_t<const char*>, std::ostream&&> );
static_assert( std::is_same_v<stream_t<int*>,        std::ostream&&> );
static_assert( std::is_same_v<stream_t<Dummy>,       std::ostream&&> );

are valid because here the global function template returning rvalue-reference:

template< class Ostream, class T >
Ostream&& operator<<( Ostream&& os, const T& value );

is selected, see (3) in https://en.cppreference.com/w/cpp/io/basic_ostream/operator_ltlt2

And these checks

static_assert( std::is_same_v<stream_t<int>,   std::ostream&> );
static_assert( std::is_same_v<stream_t<float>, std::ostream&> );

are satisfied because member functions basic_ostream<T>::operator<< are preferred for int and float arguments, and these member functions return lvalue-reference: https://en.cppreference.com/w/cpp/io/basic_ostream/operator_ltlt

Outlier answered 11/1, 2022 at 11:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.