Home > Software engineering >  How to check if a variable can be written into a stream
How to check if a variable can be written into a stream

Time:11-17

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?

CodePudding user response:

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.

CodePudding user response:

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) {
    ...
}
  • Related