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