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; } }; ```

Original source