Search code examples
c++17template-meta-programmingsfinaeenable-ifvoid-t

Combining void_t and enable_if?


In C++17, void_t allow to easily do SFINAE with class/struct templates:

template <class T, class = void>
struct test {
    static constexpr auto text = "general case";
};

template <class T>
struct test<T, std::void_t<decltype(std::begin(std::declval<T>())>> {
    static constexpr auto text = "has begin iterator";
};

What's inside void_t is a type. My question is: how to do the same, when what's inside void_t is a type trait. Using enable_if works well:

template <class T>
struct test<T, std::void_t<std::enable_if_t<std::is_class_v<T>>> {
    static constexpr auto text = "is a class";
};

Is there a shorter/more elegant way to write this, or "the right way" to do it, is really to combine void_t and enable_if?


Solution

  • A important point of std::void_t is that is variadic

    // ................VVV  <- is variadic
    template <typename ...>
    using void_t = void;
    

    so permit the SFINAE works when you have to check a multiplicity of types and permit you a soft fail when only one of them fail.

    In a case when you have to check only a value and you have to check it with std::enable_if (or a similar type trait) I don't see reason to use it together with std::void_t.

    So, in your example, "the right way" (IMHO) is avoid the use of std::void_t

    template <class T>
    struct test<T, std::enable_if_t<std::is_class_v<T>>
     { static constexpr auto text = "is a class"; };
    

    Also in the case of the use of a single decltype() I prefer the old way (but I suppose it's a question of personal taste)

    template <class T>
    struct test<T, decltype(std::begin(std::declval<T>(), void())>
     { static constexpr auto text = "has begin iterator"; };