I have a traits class which defines types for "ranges" (or containers, sequences) by deducing the type of member functions, like this:
template<class R>
struct range_type_traits
{
// "iterator": The type of the iterators of a range
using iterator = decltype(std::begin(std::declval<R>()));
// "value_type": The (non-reference) type of the values of a range
using value_type = typename std::remove_reference<decltype(*(std::declval<iterator>()))>::type;
};
The reason I do this (and not using the subtypes of R
directly or std::iterator_traits
) is to support any type of container in some templated library that has a begin()
member and doesn't require the container to have some value_type
/ iterator
types defined. As far as I know, std::iterator_traits
can't handle some kind of "key type" for containers which don't expose their iterator interface to STL using pairs, like std::map
does (example: QMap<K,T>
has value_type = T
. You can access the key via iterator::key()
.).
Now I want to conditionally define a type key_type
iif the iterator
has a function ::key() const
and take its return type, similar to what I do with the value_type
. If I just put the definition in the existing traits class, compilation fails for containers not supporting this.
SFINAE with std::enable_if
can conditionally enable template functions. How to conditionally extend an existing class / conditionally define a sub-type?
Something like this sketch:
template<class R>
struct range_type_traits
{
// "iterator": The type of the iterators of a range
using iterator = decltype(std::begin(std::declval<R>()));
// "value_type": The (non-reference) type of the values of a range
using value_type = typename std::remove_reference<decltype(*(std::declval<iterator>()))>::type;
ENABLE_IF_COMPILATION_DOES_NOT_FAIL {
// "key_type": The (non-reference) type of the keys of an associative range not using pairs in its STL-interface
using key_type = typename std::remove_reference<decltype(std::declval<iterator>().key())>::type;
}
};
You could use SFINAE on class templates to create a base class template that defines key_type
if and only if the condition you require is satisfied:
namespace detail
{
// Primary template (does not define key_type)
template<typename R, typename = void>
struct key_type_definer { };
// Specialization using SFINAE to check for the existence of key() const
// (does define key_type)
template<typename R>
struct key_type_definer<
R,
typename std::enable_if<
std::is_same<
decltype(std::declval<R const>().key()),
decltype(std::declval<R const>().key())
>::value
>::type
>
{
// Type alias definition
using key_type = typename std::remove_reference<
decltype(std::declval<R const>().key())
>::type;
};
} // end namespace detail
Then, you could derive your range_traits
class template from the key_type_definer
class template, this way:
template<class R>
struct range_type_traits : detail::key_type_definer<R>
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
{
// ...
};
range_type_traits
will now define a type alias called key_type
if and only if R
has a member function R key() const
, where R
will be the type aliased by key_type
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With