简体   繁体   中英

How to check if a variable can be written into a stream

I'm developing a tool class with C++14, which allows developers to print all kinds of objects easily.

For the std::map object, I try to develop such a function:

template<typename M, typename = std::enable_if_t<
                                std::is_same<M, std::map<typename M::key_type, typename M::mapped_type>>::value ||
                                std::is_same<M, std::unordered_map<typename M::key_type, typename M::mapped_type>>::value>>
std::string map2String(const M& mp) {
    std::stringstream res;
    for (const auto& element : mp) {
        res << element.first << "," << element.second << "|";
    }
    return res.str();
}

It works as expected. (BTW, C++11 is acceptable too, I can use the way of C++11 to replace std::enable_if_t .)

However, as you see the std::stringstream has been used, which means that M::key_type and M::mapped_type must be streamed.

But I don't know how to check if they are streamed. I need something like this:

std::enable_if_t<is_streamable<typename M::key_type>>::value

But there is no is_streamable in the Standard Library. If there is no is_streamable , is there any other way to make a compile-time error for those types which can't be written into a stream while invoking my function?

You can check whether they can be used as operand with std::ostream for operator<< .

template<typename M, typename = std::enable_if_t<
                                std::is_same<M, std::map<typename M::key_type, typename M::mapped_type>>::value ||
                                std::is_same<M, std::unordered_map<typename M::key_type, typename M::mapped_type>>::value>,
                     typename = decltype(std::declval<std::ostream>() << std::declval<typename M::key_type>()),
                     typename = decltype(std::declval<std::ostream>() << std::declval<typename M::mapped_type>())>
std::string map2String(const M& mp) {
    ...
}

Here's a simple way to make your own type trait in C++14:

#include <type_traits>
#include <utility>

namespace is_streamable_impl {
    template <typename T, typename Enable = void>
    struct check : public std::false_type {};

    template <typename T>
    struct check<T,
        std::enable_if_t<std::is_same<
            decltype(std::declval<std::ostream&>() << std::declval<T>()),
            std::ostream&>::value>>
    : public std::true_type {};
}

template <typename T>
struct is_streamable : public std::integral_constant<bool, check<T>::value> {};

This trait checks that os << val is a valid lvalue expression of type std::ostream , given an lvalue expression os of type std::ostream and an expression val with type and value category related to T . Other variations on the exact requirements could be written too.

The definition of is_streamable_impl::check above might actually be good enough as the trait directly. But it does allow someone to misuse it as check<bad_type, void>::value == true . You could just call that a bad usage and not worry about it; or this pattern here makes sure the extra template parameter is hidden away.

For your sample use, I'd actually check is_streamable<const typename M::key_type&> . That will probably will have the same result as just the direct key_type , but it's more precise for strange cases where it could matter.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM