0

I aim to implement a structure template that can be used to detect either if a template substitution is well formed or will fail. An example of usage is to provide two versions of template functions depending on whether the template parameter is comparable or not.

It can be solved quite easily if one provides structures for each scenario explicitly, e.g. whether there exists an equality operator for the template type, as shown here. But I failed to implement a structure that would accept (almost) arbitrary construct as a template argument.

The "best" approach I have reached so far uses template template argument. It compiles, but it does not fit the case when the argument substitution should be well formed.

#include <iostream>
#include <type_traits>

template <typename T = void, typename...>
using Enable = T;

template <bool Cond, typename T = void>
using Enable_if = typename std::enable_if<Cond, T>::type;

template <typename T, template<typename> class X, typename = void>
struct Is_enabled : std::false_type {};

template <typename T, template<typename> class X>
struct Is_enabled<T, X, Enable<X<T>>> : std::true_type {};

/// An example of construct
template <typename T>
using Equals = decltype(std::declval<T>() == std::declval<T>());

template <typename T>
using Enabled_eq = Enable_if<Is_enabled<T, Equals>::value>;

template <typename T>
using Disabled_eq = Enable_if<!Is_enabled<T, Equals>::value>;

template <typename T>
Enabled_eq<T> foo()
{
    std::cerr << "enabled!" << std::endl;
}

template <typename T>
Disabled_eq<T> foo()
{
    std::cerr << "disabled!" << std::endl;
}

struct A {};

int main(int /*argc*/, const char* /*argv*/[])
{
    foo<int>();  /// should print "enabled!"
    foo<A>();    /// should print "disabled!"
    return 0;
}

In case of int, it should obviously print "enabled!", and in case of A it should print "disabled!". But it always prints "disabled!", so the specialization of Is_enabled is never done.

Am I somewhat close to a correct solution, or will it be more complicated?

1 Answers1

2

The third template parameter of Is_enabled defaults to void. This is what the compiler will use in the Is_enabled<T, Equals> instantiation. That is, Is_enabled<T, X, Enable<X<T>>> : std::true_type {}; can be used only if Enable<X<T>> evaluates to void. By explicitly passing a template argument X<T> to class template Enable declared as:

template <typename T = void, typename...>
using Enable = T;

you actually create an alias for X<T> itself, and the void type (the default one, needed for the dispatching to work) is not used at all. In your case, X<T> is the result of the decltype specifier. For foo<A>() it does lead to instantiation failure. For foo<int>(), however, you get the result type of integers comparison which is bool. That is, although there is no subsitution failure, the compiler cannot use the class template specialization, because it is specialized for void, not bool.

In order to fix the code, you should rewrite Enable to always result with void:

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

This is also known as std::void_t.

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160