Nicolas Holthaus
Nicolas Holthaus

Reputation: 8273

Traits class as a template template parameter

I have traits classes sprinkled about my code which follow the same basic idiom:

template<class Frame, typename = void>
struct frame_traits
{
    typedef void base_frame_type;
};

template<class Frame>
struct frame_traits<Frame, typename std::void_t<
    typename Frame::base_frame_type>::type>
{
    typedef typename Frame::base_frame_type         base_frame_type;
};

and I have a bunch of trait checkers which use them, which also follow a similar idiom:

template <typename T>
struct has_base_frame_type : std::integral_constant<bool,
    !std::is_same<typename frame_traits<T>::base_frame_type, void>::value>::type {};

however, it turns out that has_base_frame_type has become useful to multiple concepts in my code, and I'd like to generalize it further so that I can pass the traits class as an additional parameter:

template <typename T, template<typename> class Traits = frame_traits>
struct has_base_frame_type : std::integral_constant<bool,
    !std::is_same<typename Traits<T>::base_frame_type, void>::value>::type {};

This doesn't work though, since templates with default arguments cannot be used as template template parameters.

I know I could work around the problem if I always use a traits class in the template instantiation (and modify the trait checker to accept it), namely

has_base_frame_type<frame_traits<MyClass>>::value

but I don't want to do that, because it would be all too easy to forget and pass in a non-trait class. In fact, that's how I originally had the code written until I forgot the trait one too many times and refactored it.

Is there someway I can modify my trait class idiom to work around the template template parameter problem?

Upvotes: 2

Views: 1362

Answers (1)

Piotr Skotnicki
Piotr Skotnicki

Reputation: 48457

Framework:

#include <type_traits>

template <typename...>
using void_t = void;

template <typename AlwaysVoid, template <typename...> class Operation, typename... Args>
struct detect_impl : std::false_type {};

template <template <typename...> class Operation, typename... Args>
struct detect_impl<void_t<Operation<Args...>>, Operation, Args...> : std::true_type {};

template <template <typename...> class Operation, typename... Args>
using detect = detect_impl<void, Operation, Args...>;

Detectors:

template <class Frame>
using frame_traits = typename Frame::base_frame_type;

template <class Frame>
using other_frame_traits = typename Frame::other_frame_type;

Trait with a default detector:

template <typename T, template <typename...> class Traits = frame_traits>
using has_frame_type = detect<Traits, T>;

Test:

struct A
{
    using base_frame_type = void;
};

struct B
{
    using other_frame_type = void;
};

int main()
{
    static_assert(has_frame_type<A>{}, "!");  // default
    static_assert(!has_frame_type<B>{}, "!"); // default

    static_assert(!has_frame_type<A, other_frame_traits>{}, "!"); // non-default
    static_assert(has_frame_type<B, other_frame_traits>{}, "!");  // non-default
}

DEMO

Upvotes: 2

Related Questions