Подтвердить что ты не робот

Как я могу определить, может ли тип быть потоковым в std:: ostream?

Я пытаюсь написать характер типа, чтобы определить, имеет ли тип перегруженный оператор < <(), подходящий для использования в выходной поток.

Мне что-то не хватает, потому что я всегда верю в простой пустой класс без каких-либо операторов.

Здесь код:

template<typename S, typename T>
class is_streamable
{
    template<typename SS, typename TT>
    static auto test(SS&& s, TT&& t)
    -> decltype(std::forward<SS>(s) << std::forward<TT>(t));

    struct dummy_t {};
    static dummy_t test(...);

    using return_type = decltype(test(std::declval<S>(), std::declval<T>()));

public:
    static const bool value = !std::is_same<return_type, dummy_t>::value;
};

class C {};

int main() {
    std::cout << is_streamable<std::stringstream, C>::value << std::endl;
    return 0;
}

Вывод:

1

Здесь он находится в идеоне: https://ideone.com/ikSBoT

Что я делаю неправильно?

4b9b3361

Ответ 1

По-видимому, эта перегрузка operator<<, которая наступает на вашем пути и делает выражение в действии типа traling действительным:

template< class CharT, class Traits, class T >
basic_ostream< CharT, Traits >& operator<<( basic_ostream<CharT,Traits>&& os,
                                            const T& value );

См. (3) на этой справочной странице. Это простой форвардер (вызывающий os << value), который был добавлен в С++ 11, чтобы разрешить вставку в rvalue-потоки, потому что они не привязаны к перегрузкам, беря ссылку lvalue.

Таким образом, проблема заключается в том, что std::declval<SS>() возвращает ссылку rvalue, и эта перегрузка начинается. Сам вызов хорошо сформирован, но поскольку сама функция не создается, вы не получаете ошибку, даже если значение равно не поддаются потоку.

Это можно обойти стороной, если вы явно запрашиваете ссылку на lvalue: std::declval<SS&>().

Я бы предложил немного другую реализацию, не передавая поток и значение test. Вы можете использовать declval непосредственно внутри decltype. Вместе с оператором запятой он выглядит так:

#include <type_traits>
#include <utility>
#include <iostream>
#include <sstream>

template<typename S, typename T>
class is_streamable
{
    template<typename SS, typename TT>
    static auto test(int)
    -> decltype( std::declval<SS&>() << std::declval<TT>(), std::true_type() );

    template<typename, typename>
    static auto test(...) -> std::false_type;

public:
    static const bool value = decltype(test<S,T>(0))::value;
};

class C {};

int main() {
    std::cout << is_streamable<std::stringstream, C>::value << std::endl;
    return 0;
}

Ответ 2

Я не совсем уверен, в чем проблема, но он работает, если вы удалите std::forward s, и я не думаю, что они нужны здесь в любом случае:

template<typename SS, typename TT>
static auto test(SS&& s, TT&& t) -> decltype(s << t);

Live example

Ответ 3

"Вдохновленный" по ypw-ответу (ypw - если вы отредактируете ваш соответственно) или создайте новый, чтобы избавиться от downvotes - я удалю это и увеличим ваш):

template <typename T>
class is_streamable
{
    template <typename U> // must be template to get SFINAE fall-through...
    static auto test(const U* u) -> decltype(std::cout << *u);

    static auto test(...)        -> std::false_type;

 public:
    enum { value = !std::is_same<decltype(test((T*)0)), std::false_type>::value };
};

Обсуждение

Основная часть этого ответа заключается в том, чтобы подчеркнуть, насколько бессмысленно беспокоиться о ссылках rvalue/lvalue, declvar, forward и т.д. для этой проблемы. Помните, что мы просто выполняем утверждение времени компиляции, которое поддерживает потоковая нотация - нет времени выполнения для соображений эффективности во время выполнения, таких как типы ссылок на материю, и нет необходимости использовать declvar для создания потока как хотя их не было. Этот код просто прост, и я считаю, что он имеет полную полезность - доказательство обратного больше всего приветствуется.

Ответ 4

EDIT: Как найдено @jrok, существует общий оператор < < для потоков rvalue, которые плохо взаимодействуют.

Что-то действительно неправильно здесь, если вы посмотрите на код ниже, протестированный на coliru, две последние строки скомпилируются, даже если это они не должен...

std::stringstream ss;
B b;
int v;

std::cout << typeid(decltype(ss>>v )).name() << "\n" ;
std::cout << typeid(decltype(ss<<1 )).name() << "\n" ;
std::cout << typeid(decltype(std::declval<std::stringstream>()>>v )).name() << "\n" ;
std::cout << typeid(decltype(std::declval<std::stringstream>()<<1 )).name() << "\n" ;

//std::cout << typeid(decltype(ss>>b )).name() << "\n" ; // do not compile
//std::cout << typeid(decltype(ss<<b )).name() << "\n" ; // do not compile
std::cout << typeid(decltype(std::declval<std::stringstream>()>>b )).name() << "\n" ; // should not compile but succeed
std::cout << typeid(decltype(std::declval<std::stringstream>()<<b )).name() << "\n" ; // should not compile but succeed

Ответ 5

jrok answer вызывает ошибки привязки, когда значение передается функции, требующей lvalue (т.е. TheThruth(const bool& t)). Итак, теперь в С++ 17 у нас есть шаблон void_t. И на основании примера на CPPReference я написал и протестировал следующее:

#include <iostream>
#include <typeinfo>

template<typename S, typename T, typename = void>
struct is_to_stream_writable: std::false_type {};

template<typename S, typename T>
struct is_to_stream_writable<S, T,
        std::void_t<  decltype( std::declval<S&>()<<std::declval<T>() )  > >
: std::true_type {};


class Foo
{
    public:
    Foo(){}
};

void TheTruth(const bool& t)
{
    std::cout<< t<< std::endl;
}

int main() {
    std::cout<< is_to_stream_writable<std::ostream,int>::value <<std::endl;
    std::cout<< is_to_stream_writable<std::ostream,Foo>::value <<std::endl;
    TheTruth( is_to_stream_writable<std::ostream,int>::value  );

}

Также обратите внимание, что имя is_to_stream_writable лучше подходит для operator << и предлагает имя: is_from_stream_readable для operator >> (приветствуются лучшие предложения по названию).

Код компилируется с g++ -std=c++1z -O0 -Wall -pedantic main.cpp, gcc версиями 6.2 и 7.2 и Coliru.