Conditionally enable a sub-type (similar to enable_if to enable functions)
c++, c++11, sfinae, templates, type-traits
Solution
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`.
Problem
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; } }; ```