Multiple Sfinae Class Template Specialisations Using Void_T

Multiple SFINAE class template specialisations using void_t

There is a rule that partial specializations have to be more specialized than the primary template - both of your specializations follow that rule. But there isn't a rule that states that partial specializations can never be ambiguous. It's more that - if instantiation leads to ambiguous specialization, the program is ill-formed. But that ambiguous instantiation has to happen first!

It appears that clang is suffering from CWG 1558 here and is overly eager about substituting in void for std::void_t.

This is CWG 1980 almost exactly:

In an example like

template<typename T, typename U> using X = T;
template<typename T> X<void, typename T::type> f();
template<typename T> X<void, typename T::other> f();

it appears that the second declaration of f is a redeclaration of the first but distinguishable by SFINAE, i.e., equivalent but not functionally equivalent.

If you use the non-alias implementation of void_t:

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

then clang allows the two different specializations. Sure, instantiating has_members on a type that has both type1 and type2 typedefs errors, but that's expected.

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>.

using SFINAE for template class specialisation

Since you said you were still waiting for a better answer, here's my take on it. It's not perfect, but I think it gets you as far as possible using SFINAE and partial specializations. (I guess Concepts will provide a complete and elegant solution, but we'll have to wait a bit longer for that.)

The solution relies on a feature of alias templates that was specified only recently, in the standard working drafts after the final version of C++14, but has been supported by implementations for a while. The relevant wording in draft N4527 [14.5.7p3] is:

However, if the template-id is dependent, subsequent template argument substitution still applies to the template-id. [ Example:

template<typename...> using void_t = void;
template<typename T> void_t<typename T::foo> f();
f<int>(); // error, int does not have a nested type foo

—end example ]

Here's a complete example implementing this idea:

#include <iostream>
#include <type_traits>
#include <utility>

template<typename> struct User { static void f() { std::cout << "primary\n"; } };

template<typename> struct Data { };
template<typename T, typename U> struct Derived1 : Data<T*> { };
template<typename> struct Derived2 : Data<double> { };
struct DD : Data<int> { };

template<typename T> void take_data(Data<T>&&);

template<typename T, typename = decltype(take_data(std::declval<T>()))>
using enable_if_data = T;

template<template<typename...> class TT, typename... Ts>
struct User<enable_if_data<TT<Ts...>>>
{
static void f() { std::cout << "partial specialization for Data\n"; }
};

template<typename> struct Other { };
template<typename T> struct User<Other<T>>
{
static void f() { std::cout << "partial specialization for Other\n"; }
};

int main()
{
User<int>::f();
User<Data<int>>::f();
User<Derived1<int, long>>::f();
User<Derived2<char>>::f();
User<DD>::f();
User<Other<int>>::f();
}

Running it prints:

primary
partial specialization for Data
partial specialization for Data
partial specialization for Data
primary
partial specialization for Other

As you can see, there's a wrinkle: the partial specialization isn't selected for DD, and it can't be, because of the way we declared it. So, why don't we just say

template<typename T> struct User<enable_if_data<T>> 

and allow it to match DD as well? This actually works in GCC, but is correctly rejected by Clang and MSVC because of [14.5.5p8.3, 8.4] ([p8.3] may disappear in the future, as it's redundant - CWG 2033):

  • The argument list of the specialization shall not be identical to
    the implicit argument list of the primary template.
  • The specialization shall be more specialized than the primary template (14.5.5.2).

User<enable_if_data<T>> is equivalent to User<T> (modulo substitution into that default argument, which is handled separately, as explained by the first quote above), thus an invalid form of partial specialization. Unfortunately, matching things like DD would require, in general, a partial specialization argument of the form T - there's no other form it can have and still match every case. So, I'm afraid we can conclusively say that this part cannot be solved within the given constraints. (There's Core issue 1980, which hints at some possible future rules regarding the use of template aliases, but I doubt they'll make our case valid.)

As long as the classes derived from Data<T> are themselves template specializations, further constraining them using the technique above will work, so hopefully this will be of some use to you.


Compiler support (this is what I tested, other versions may work as well):

  • Clang 3.3 - 3.6.0, with -Wall -Wextra -std=c++11 -pedantic - works as described above.
  • GCC 4.7.3 - 4.9.2, same options - same as above. Curiously, GCC 5.1.0 - 5.2.0 no longer selects the partial specialization using the correct version of the code. This looks like a regression. I don't have time to put together a proper bug report; feel free to do it if you want. The problem seems to be related to the use of parameter packs together with a template template parameter. Anyway, GCC accepts the incorrect version using enable_if_data<T>, so that can be a temporary solution.
  • MSVC: Visual C++ 2015, with /W4, works as described above. Older versions don't like the decltype in the default argument, but the technique itself still works - replacing the default argument with another way of expressing the constraint makes it work on 2013 Update 4.

SFINAE and partial class template specializations

I would like to argue that the Standard does not support SFINAE in partial specializations, due to a wording defect. Let's start with [temp.class.spec.match]:

A partial specialization matches a given actual template argument list if the template arguments of the
partial specialization can be deduced from the actual template argument list (14.8.2).

And, from [temp.deduct], the SFINAE clause:

If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is
one that would be ill-formed, with a diagnostic required, if written using the substituted arguments. [ Note:
If no diagnostic is required, the program is still ill-formed. Access checking is done as part of the substitution
process. —end note ] Only invalid types and expressions in the immediate context of the function type and its template parameter types can result in a deduction failure.

The slightly-modified example from CWG is:

template <class T, class U> struct X   {
typedef char member;
};

template<class T> struct X<T,
typename enable_if<(sizeof(T)>sizeof(
float)), float>::type>
{
typedef long long member;
};

int main() {
cout << sizeof(X<char, float>::member);
}

Name lookup on X finds the primary, with T == char, U == float. We look at the one partial specialization, and see if it "matches" - which means that the template arguments "can be deduced" - which is to say:

+-------------+--------+-------------------------------------------------+
| | arg1 arg2 |
+-------------+--------+-------------------------------------------------+
| deduce T in | T | enable_if_t<(sizeof(T) > sizeof(float), float> |
| from | char | float |
+-------------+--------+-------------------------------------------------+

Normal template deduction rules apply. The second "argument" is a non-deducible context, so we deduce T as char. sizeof(char) > sizeof(float), is false, and enable_if_t<false, float> is an invalid type, so type deduction should fail... but, deduction failure can only occur

in the immediate context of the function type and its template parameter types

and we're not dealing with a function type or function template parameter types, we're dealing with class template parameter types. A class is not a function, so the SFINAE exclusion should not apply if we take everything literally - and the modified CWG example should lead to a hard error.

However, the spirit of the rule seems to be more along the lines of:

Only invalid types and expressions in the immediate context of the deduction process can result in a deduction failure.

I do not know what the reason would be to specifically exclude class partial specialization deduction. Furthermore, partial ordering of class template partial specializations also look like functions. From [temp.class.order]:

For two class template partial specializations, the first is more specialized than the second if, given the
following rewrite to two function templates, [...]

The Standard thus already in the very next section exhibits a duality between class template partial specializations and function templates. The fact that this only applies to partial specialization ordering, and not substitution failure during partial specialization argument deduction, strikes me as a defect.


The example itself was X<double, float>. But this actually doesn't demonstrate or require SFINAE, as there would be no substitution failure anywhere.

Class Template specialization for multiple types

You can use partial template specialization in combination with SFINAE to achieve this:

#include <type_traits>

template <class T, typename = void>
class State
{
T state;

public:
void set(T newState)
{
state = newState;
}

T get()
{
return state;
}
};

template <typename T>
class State<T, std::enable_if_t<std::is_arithmetic_v<T>>>
{
T state;

public:
void set(int newState)
{
state = newState;
}

int get()
{
return state;
}

int multiplyState(int n)
{
return state*n;
}
};

live example here

The trick here lies in the use of the second template parameter (which can be unnamed and is given a default argument). When you use a specialization of your class template, e.g., State<some_type>, the compiler has to figure out which of the templates should be used. To do so, it has to somehow compare the given template arguments with each template and decide which one is the best match.

The way this matching is actually done is by trying to deduce the arguments of each partial specialization from the given template arguments. For example, in the case of State<int>, the template arguments are going to be int and void (the latter is there because of the default argument for the second parameter of the primary template). We then try to deduce the arguments for our sole partial specialization

template <typename T>
class State<T, std::enable_if_t<std::is_arithmetic_v<T>>>;

from the template arguments int, void. Our partial specialization has a single parameter T, which can directly be deduced from the first template argument to be int. And with that, we're already done as we have deduced all parameters (there is only one here). Now we substitute the deduced parameters into the partial specialization: State<T, std::enable_if_t<std::is_arithmetic_v<T>>>. We end up with State<int, void>, which matches the list of initial arguments of int, void. Therefore, the partial template specialization applies.

Now, if, instead, we had written State<some_type>, where some_type is not an arithmetic type, then the process would be the same up to the point where we have successfully deduced the parameter for the partial specialization to be some_type. Again, we substitute the parameter back into the partial specialization State<T, std::enable_if_t<std::is_arithmetic_v<T>>>. However, std::is_arithmetic_v<some_type> will now be false, which will lead to std::enable_if_t<…> not being defined and substitution fails. Since substituion failure is not an error in this context, this simply means that the partial specialization is not an option here and the primary template will be used instead.

If there were multiple matching partial specializations, they then would have to be ranked to pick the best match. The actual process is quite complicated, but it generally boils down to picking the most concrete specialization.

c++ SFINAE - checking class has typedef, why is void_t required?

you cannot have pointer to reference

using X = int&;
using T = X*; // fail

so at least U::reference* is pretty likely to fail.


I don't think NULLs are really necessary since the caller do provide all the value.



note: I'm assuming __void_t is the same as std::void_t


accessing class member regardless of it being a function or a data member

You seem to be reinventing std::invoke. This function embodies the definition of Callable concept, and that definition has two special cases:

  • a pointer to data member is "callable" like a function taking the object as its single parameter: std::invoke(&C::dataMember, obj) is equivalent to obj.*dataMember
  • a pointer to member function is "callable" like a function taking the object as its first parameter: std::invoke(&C::memFun, obj, a, b, c) is equivalent to (obj.*memFun)(a, b, c)

Putting this together, your name can be implemented simply as

template <typename T>
decltype(auto) name(const T &t)
{
return std::invoke(&T::name, t);
}

It will do the right thing whether name is a data member or a member function. Demo


If you want _name as a customization point, just add an extra indirection: make name call _name, and the default implementation of _name call std::invoke.

Using `void_t` to detect multiple inheritance type repetition errors

As @Canoninos noted, the problem is that:

it isn't the declaration of dup_helper<T, T> which causes an error but its definition [...].

Or, in Standardese, the error occurs outside the "immediate context" ([temp.deduct]) of the substitution:

8 - [...] Only invalid types and expressions in the immediate context of the function type and
its template parameter types can result in a deduction failure. [ Note: The evaluation of the substituted types
and expressions can result in side effects such as the instantiation of class template specializations and/or
function template specializations, the generation of implicitly-defined functions, etc. Such side effects are
not in the “immediate context” and can result in the program being ill-formed. — end note ]

Here the error occurs while instantiating dup_helper<float, float> so is not in the "immediate context".

One multiple inheritance trick that's very close to yours involves adding an extra layer of inheritance, by indexing the multiple bases:

helper<<0, 1>, <float, float>>        
+
+----+----+
v v
ix<0, float> ix<1, float>
+ +
v v
t<float> t<float>

This gives us a helper class with a valid definition and that can be instantiated but not cast to its ultimate base classes, because of ambiguity:

static_cast<t<float>>(helper<...>{});  // Error, SFINAE-usable

Example.

Combine multiple class template specializations

Just to make fun with variadic templates, I propose a couple of solutions.

Both of they are based over a constexpr function that say if a value is in a template variadic list (as value_in_list in aschepler's answer but this works also in C++14)

template <typename T, T ... ts>
constexpr bool isInList (T const & t0)
{
using unused = bool[];

bool ret { false };

(void)unused { false, ret |= t0 == ts... };

return ret;
}

The first one is very similar to the aschepler's solution (+1) and doesn't use template specialization.

template <std::underlying_type_t<PositiveDigit> I>
struct IsNum1
{
static constexpr bool aPrimeDigit
= isInList<PositiveDigit, PositiveDigit::Two, PositiveDigit::Three,
PositiveDigit::Five, PositiveDigit::Seven>
(static_cast<PositiveDigit>(I));
};

It seems to me the simpler one but if you really (really!) want to pass through template specialization, you can write something as follows

template <std::underlying_type_t<PositiveDigit>, typename = std::true_type>
struct IsNum2
{ static constexpr bool aPrimeDigit = false; };

template <std::underlying_type_t<PositiveDigit> I>
struct IsNum2<I, std::integral_constant<bool, isInList<
PositiveDigit, PositiveDigit::Two, PositiveDigit::Three,
PositiveDigit::Five, PositiveDigit::Seven>
(static_cast<PositiveDigit>(I))>>
{ static constexpr bool aPrimeDigit = true; };

The following is a full compiling example

#include <type_traits>

enum class PositiveDigit
{ Zero, One, Two, Three, Four, Five, Six, Seven, Eight, Nine };

template <typename T, T ... ts>
constexpr bool isInList (T const & t0)
{
using unused = bool[];

bool ret { false };

(void)unused { false, ret |= t0 == ts... };

return ret;
}

template <std::underlying_type_t<PositiveDigit> I>
struct IsNum1
{
static constexpr bool aPrimeDigit
= isInList<PositiveDigit, PositiveDigit::Two, PositiveDigit::Three,
PositiveDigit::Five, PositiveDigit::Seven>
(static_cast<PositiveDigit>(I));
};

template <std::underlying_type_t<PositiveDigit>, typename = std::true_type>
struct IsNum2
{ static constexpr bool aPrimeDigit = false; };

template <std::underlying_type_t<PositiveDigit> I>
struct IsNum2<I, std::integral_constant<bool, isInList<
PositiveDigit, PositiveDigit::Two, PositiveDigit::Three,
PositiveDigit::Five, PositiveDigit::Seven>
(static_cast<PositiveDigit>(I))>>
{ static constexpr bool aPrimeDigit = true; };

int main ()
{
static_assert( false == IsNum1<-5>::aPrimeDigit, "!" );
static_assert( false == IsNum1<13>::aPrimeDigit, "!" );
static_assert( true == IsNum1< 7>::aPrimeDigit, "!" );

static_assert( false == IsNum2<-5>::aPrimeDigit, "!" );
static_assert( false == IsNum2<13>::aPrimeDigit, "!" );
static_assert( true == IsNum2< 7>::aPrimeDigit, "!" );
}


Related Topics



Leave a reply



Submit