Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conditionally enable a sub-type (similar to enable_if to enable functions)

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;
    }
};
like image 532
leemes Avatar asked Apr 14 '13 13:04

leemes


1 Answers

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.

like image 171
Andy Prowl Avatar answered Sep 28 '22 06:09

Andy Prowl