Why Void_T Doesnt Work in Sfinae But Enable_If Does

Why void_t doesnt work in SFINAE but enable_if does

This seems to be related to CWG issue #1980 (credits to T.C. for correcting me).

As a workaround you can define void_t as:

template<typename... Ts> struct make_void { typedef void type;};
template<typename... Ts> using void_t = typename make_void<Ts...>::type;

(from cppreference)

live example on wandbox

Combining void_t and enable_if?

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

Why does enable_if* = nullptr work when enable_if = void doesn't?

The first set of variants you are just setting the value of a template type argument. Two overloads with different values for a template type argument collide, as they are both of kind template<class,class> and have the same function arguments.

The non-type template argument cases, the ones where you use a raw enable if you end up having a template non type argument of type void. That is illegal; the various error messages are the various ways it is illegal.

When you add a star, when the enable if clause passes it is a template non type argument of type void pointer.

When it fails, it isn't an argument at all.

An equivalent to the nullptr case is:

std::enable_if_t<std::is_base_of_v<Base, T>, bool> = true

when the clause is true, the enable if evaluates to bool, and we get:

bool = true

a template non-type argument of type bool that defaults to true. When the clause (the base of clause) is false, we get a SFINAE failure; there is no template type or non-type argument there.


With the class Whatever = enable_if cases we are trying SFINAE based on default value of template arguments. This leads to signature collision, because signatures have to be unique if they are found during overload resolution (in the same phase).

With the enable = value cases, we are trying SFINAE based on if there is a template non-type argument there. On failure, there is no signature to compare, so it cannot collide.

What remains is to make the syntax simple and pretty.

Now, this is all obsolete with Concepts, so don't fall in love with the syntax.

Why does this SFINAE not work with enable_if when one conditional branch is inherited from the base class?

As always with such problems, it falls down to the very definition of SFINAE. The S stands for "substitution", which happens into the template that we are trying to instantiate. That template is the member f, and not C.

Even though C is a template also, and both A and B are dependent types in C, they are not dependent type when f is instantiated. They are already known. As such, the condition std::is_same<A, B>::value is not value dependent on any template parameter of f. It doesn't depend on substation into f. This trips the following clause in the C++11 standard (taken from the last draft prior to publication):

[temp.res] (emphasis mine)

8 Knowing which names are type names allows the syntax of every
template definition to be checked. No diagnostic shall be issued for a
template definition for which a valid specialization can be generated.
If no valid specialization can be generated for a template definition, and that template is not instantiated, the template
definition is ill-formed, no diagnostic required
.

This means that whatever Types is, if it doesn't uphold the condition of f, then the very definition of f (without even being instatiated), is already ill-formed whenever C is instantiated. A diagnostic is not required for this in general (since checking it is intractable in the general case), but compilers can diagnose it early often enough, and will tell you about the problem.

Now, as to how to fix it, just make the condition of f value dependent on its own template parameter. A simple re-write can be

template <bool Dummy = std::is_same<A, B>::value>
inline auto f(vector<int>& ctx, const string& r) ->
typename std::enable_if<Dummy>::type { }

Now the condition depends on substation in the correct context.


Of course, even if you fix the SFIANE problem, you still need to make sure the overload set is composed of the correct members. The f in C2 hides the f in C1. Add a using declaration to C2 so it's still a candidate

using C1<Types>::f;

enable_if for class template specialization with argument other than void

Specializations are irrelevant until the compiler knows which types it is going to use for the primary template.

When you write A<double>, then the compiler looks only at the primary template and sees that you actually mean A<double,void>.

And only then it is looking for specializations. Now, when your specialization is for A<double,int>, then it is not suitable because you asked for A<double,void>.

why SFINAE (enable_if) works from inside class definition but not from outside

That would be because a templated function in a templated class has two sets of template parameters, not one. The "correct" form is thus:

template<typename T, int N>
class A
{
public:
void f(void);

template<typename std::enable_if<N == 2, void>::type* = nullptr>
void g(void);
};

template<typename T, int N> // Class template.
template<typename std::enable_if<N == 2, void>::type* /* = nullptr */> // Function template.
inline void A<T, N>::g()
{
std::cout << "g()\n";
}

See it in action here.

[Note that this isn't actually correct, for a reason explained at the bottom of this answer. It'll break if N != 2.]

Continue reading for an explanation, if you so desire.


Still with me? Nice. Let's examine each situation, shall we?

  1. Defining A<T, N>::g() outside A:

    template<typename T, int N>
    class A
    {
    public:
    void f(void);
    void g(void);
    };

    template<typename T, int N, typename std::enable_if<N == 2, void>::type* = nullptr>
    inline void A<T, N>::g()
    {
    std::cout << "g()\n";
    }

    In this case, A<T, N>::g()'s template declaration doesn't match A's template declaration. Therefore, the compiler emits an error. Furthermore, g() itself isn't templated, so the template can't be split into a class template and a function template without changing A's definition.

    template<typename T, int N>
    class A
    {
    public:
    void f(void);

    // Here...
    template<typename std::enable_if<N == 2, void>::type* = nullptr>
    void g(void);
    };

    // And here.
    template<typename T, int N> // Class template.
    template<typename std::enable_if<N == 2, void>::type* /* = nullptr */> // Function template.
    inline void A<T, N>::g()
    {
    std::cout << "g()\n";
    }
  2. Defining A<T, N>::g() inside A:

    template<typename T, int N>
    class A
    {
    public:
    void f(void);

    template<typename t = T, int n = N, typename std::enable_if<N == 2, void>::type* = nullptr>
    void g()
    {
    std::cout << "g()\n";
    }
    };

    In this case, since g() is defined inline, it implicitly has A's template parameters, without needing to specify them manually. Therefore, g() is actually:

    // ...
    template<typename T, int N>
    template<typename t = T, int n = N, typename std::enable_if<N == 2, void>::type* = nullptr>
    void g()
    {
    std::cout << "g()\n";
    }
    // ...

In both cases, for g() to have its own template parameters, while being a member of a templated class, the function template parameters have to be separated from the class template parameters. Otherwise, the function's class template wouldn't match the class'.


Now that we've covered that, I should point out that SFINAE only concerns immediate template parameters. So, for g() to use SFINAE with N, N needs to be its template parameter; otherwise, you'd get an error if you tried to call, for example, A<float, 3>{}.g(). This can be accomplished with an intermediary, if necessary.

Additionally, you'll need to provide a version of g() that can be called when N != 2. This is because SFINAE is only applicable if there's at least one valid version of the function; if no version of g() can be called, then an error will be emitted and no SFINAE will be performed.

template<typename T, int N>
class A
{
public:
void f(void);

// Note the use of "MyN".
template<int MyN = N, typename std::enable_if<MyN == 2, void>::type* = nullptr>
void g(void);

// Note the "fail condition" overload.
template<int MyN = N, typename std::enable_if<MyN != 2, void>::type* = nullptr>
void g(void);
};

template<typename T, int N>
template<int MyN /*= N*/, typename std::enable_if<MyN == 2, void>::type* /* = nullptr */>
inline void A<T, N>::g()
{
std::cout << "g()\n";
}

template<typename T, int N>
template<int MyN /*= N*/, typename std::enable_if<MyN != 2, void>::type* /* = nullptr */>
inline void A<T, N>::g()
{
std::cout << "()g\n";
}

If doing this, we can further simplify things, by having the intermediary do the heavy lifting.

template<typename T, int N>
class A
{
public:
void f(void);

template<bool B = (N == 2), typename std::enable_if<B, void>::type* = nullptr>
void g(void);

template<bool B = (N == 2), typename std::enable_if<!B, void>::type* = nullptr>
void g(void);
};

// ...

See it in action here.

Enable_if problems

Two issues I see:

  • You're writing std::enable_if<> instead of std::enable_if<>::type, which is probably not what you meant to do.

  • (The cause of the error) You have two return types: std::enable_if<> and void. You can only have one return type.

std::enable_if has a second optional type parameter that is the the type of std::enable_if<>::type. This is set by default to void so your return type will already be void from std::enable_if<>::type. However since you're putting enable_if in the return type I'd recommend you explicitly specify that the return type be void.

So the solution to your issue would look something like this:

template <class RandomAccessIterator, class Compare>
typename std::enable_if<
std::is_same<std::random_access_iterator_tag, RandomAccessIterator>::value,
void
>::type quick_sort(RandomAccessIterator first, RandomAccessIterator last, Compare compare) {
sorting(first, last, 0, last - first - 1, compare);
}

Alternatively you could use std::enable_if in a template parameter:

template <
class RandomAccessIterator,
class Compare,
typename std::enable_if<
std::is_same<std::random_access_iterator_tag, RandomAccessIterator>::value,
std::nullptr_t
>::type = nullptr
>
void quick_sort(RandomAccessIterator first, RandomAccessIterator last, Compare compare) {
sorting(first, last, 0, last - first - 1, compare);
}

Why do `SFINAE` (std::enable_if) uses bool literals instead of `true_t` / `false_t` tag classes?

The very 1st thing I have a "problem" with, is bool literal (true/false). I understand they are correct and templates can accept compile-time constant values of primitive data types (plain-old-data types) but if I were tasked to design the enable_if "mechanisms" instead of using true/false I would create a tag classes true_t(or True) and false_t (or False) as follows

The issue with use a tag type instead of just a bool is that you have to add extra complexity to the code. If you want to check a compile time condition, like sizeof for instance, you couldn't just do sizeof(T) == 8. You would have to make an abstraction that does the check and the returns the appropriate tag type.

The second thing I find redundant is the need to specify typename T template parameter. Wouldn't it be easier / better to just implement enable_if as follows

Not really. What if you want to use the SFINAE for the return type? You would only be able to have a void function then, which is unnecessarily limiting. Instead what you can do is use what was later added in C++14 and C++17 and make aliases. This makes the names non dependent and lets you drop the typename

template< bool B, class T = void >
using enable_if_t = typename enable_if<B,T>::type;
template< class T >
inline constexpr bool is_integral_v = is_integral<T>::value;

This allows you to rewrite

template <class T,
typename std::enable_if<std::is_integral<T>::value,T>::type* = nullptr>
void do_stuff(T& t) { /* do stuff */ };

to

template <class T,
std::enable_if_t<std::is_integral_v<T>,T>* = nullptr>
void do_stuff(T& t) { /* do stuff */ };

although I prefer to use a bool for the type of enable_if_t like

template <class T,
std::enable_if_t<std::is_integral_v<T>, bool> = true>
void do_stuff(T& t) { /* do stuff */ };

I know that, on this site, I am obligated to ask a single question within a... single "question-post-like" format, but if you find it acceptable could I also ask what will this syntax:

std::enable_if</* ... */>::type* = nullptr

accomplish?

It makes a pointer to the type that std::enable_if "returns" and sets it to null pointer. The goal here is to make a template parameter that will only exist if the condition is true. You could rewrite it to

typename = typename std::enable_if</* ... */>::type

so instead of having a non type parameter you have a type parameter. They both accomplish the same thing but the latter wont work with overloading the function for different enable_if's since default template parameters are not part of the signature. The first version which uses non type parameters is included in the function signature and does allow you to overload the enable_if's.



Related Topics



Leave a reply



Submit