Using Sfinae for Template Class Specialisation

Template specialization for SFINAE

That is simply how SFINAE works ;)

As you know, you have to "create a failure" within the template declaration and not inside the template definition. As this:

template < typename X, typename = ... here is the code which may generate an error     during instantiation >
void Bla() {}

The only chance to put some "code" in the declaration is to define something in the template parameter list or inside the template function declaration itself like:

template < typename X>
void Bla( ... something which may generates an error ) {}

Example:

template <typename T, typename = std::enable_if_t< std::is_same_v< int, T>>>
void Bla()
{
std::cout << "with int" << std::endl;
}

template <typename T, typename = std::enable_if_t< !std::is_same_v< int, T>>>
void Bla(int=0)
{
std::cout << "with something else" << std::endl;
}

int main()
{
Bla<int>();
Bla<std::string>();
}

But what is the background of "creating an substitution failure" here?

The trick is somewhere behind std::enable_if. We can also use it without that:

template <typename T, typename = char[ std::is_same_v< int, T>]>
void Bla()
{
std::cout << "with int" << std::endl;
}

template <typename T, typename = char[ !std::is_same_v< int, T>]>
void Bla(int=0)
{
std::cout << "with something else" << std::endl;
}

Take a look on: typename = char[ !std::is_same_v< int, T>]
Here std::is_same_v gives us a bool value back which is casted to 0 if not valid and to any postive number if valid. And creating a char[0] is simply an error in c++! So with the negation of our expression with ! we got one for "is int" and one for "is not int". Once it tries to create an array of size 0, which is a failure and the template will not be instantiated and once it generates the type char[!0] which is a valid expression.

And as the last step:

... typename = ...

is meant as: there will be defined a type, here without a template parameter name, as the parameter itself is not used later. You also can write:

... typename X = ... 

but as X is not used, leave it!

Summary: You have to provide some code, which generates an error or not, depending on the type or value of a given expression. The expression must be part of the function declaration or part of the template parameter list. It is not allowed to put an error inside the function/class definition, as this will not longer be "not an error" in the sense of SFINAE.

Update: Can the results of SFINAE expressions be used for further expressions: Yes

Example:

    template < typename TYPE >
void CheckForType()
{
std::cout << __PRETTY_FUNCTION__ << std::endl;
}

template <typename T, typename X = std::enable_if_t< std::is_same_v< int, T>, float>>
void Bla()
{
std::cout << "with int" << std::endl;
CheckForType<X>();
}

template <typename T, typename X = std::enable_if_t< !std::is_same_v< int, T>, double >>
void Bla(int=0)
{
std::cout << "with something else" << std::endl;
CheckForType<X>();
}

int main()
{
Bla<int>();
Bla<std::string>();
}

Output:

with int
void CheckForType() [with TYPE = float]
with something else
void CheckForType() [with TYPE = double]

Explanation:
std::enable_if has a second template parameter which is used as return type, if the first parameter of it will be true. As this, you can use that type here. As you can see, the function CheckForType is called with this defined type for X from the SFINAE expression.

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.

Is it possible to mix SFINAE and template specialisation?

Direct answer to your question

You can, but you really can't. Your case is complicated by variadic template arguments.

// specialisation for arithmetic types
template<class AirthmeticT, class... Args>
struct ArgsEstimate<
AirthmeticT,
std::enable_if_t<std::is_arithmetic_v<AirthmeticT>>,
Args...>
{
static const std::size_t size = sizeof(AirthmeticT) + ArgsEstimate<Args...>::size;
};

This works... sort of. You just need to make sure the second parameter is always void:

ArgsEstimate<int, void, /* ... */> ok; // will use the integer specialization

ArgsEstimate<int, int, int> wrong; // oups, will use the base template.

This is impractical.

C++20 concepts

Concepts elegantly solve this:

// specialisation for arithmetic types
template<class T, class... Args>
requires std::is_arithmetic_v<T>
struct ArgsEstimate<T, Args...>
{
static const std::size_t size = sizeof(T) + ArgsEstimate<Args...>::size;
};


The pre-concepts solution

What you need to do is to split your class into two classes. One that defines the size just for 1 argument. Here you can use SFINAE. And the other one that summs them:

template <class T, class Enable = void>
struct ArgEstimate {};

// specialisation for string, SFINAE would be overkill
template<>
struct ArgEstimate<std::string&>
{
static const std::size_t size = 64;
};

// specialisation for arithmetic types
template<class T>
struct ArgEstimate<T, std::enable_if_t<std::is_arithmetic_v<T>>>
{
static const std::size_t size = sizeof(T);
};

// specialisation for pointer types
template <class T>
struct ArgEstimate<T*>
{
static const std::size_t size = 32;
};
// the declaration
template<class... Args> struct ArgsEstimate;

template<class T>
struct ArgsEstimate<T>
{
static const std::size_t size = ArgEstimate<T>::size;
};

template<class Head, class... Tail>
struct ArgsEstimate<Head, Tail...>
{
static const std::size_t size = ArgEstimate<Head>::size + ArgsEstimate<Tail...>::size;
};

And if you have C++17 you can use fold expression to simplify the sum:

template<class... Args>
struct ArgsEstimate
{
static const std::size_t size = (... + ArgEstimate<Args>::size);
};

Also just wanted to point out that you don't need SFINAE for pointers:

// specialisation for pointer types
template <class T, class... Args>
struct ArgsEstimate<T*, Args...> {
static const std::size_t size = 32 + ArgsEstimate<Args...>::size;
};

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.

C++ SFINAE partial specialization

Usually this is done with specialization and a template defaulted parameter.

I mean

template <typename, typename = void>
struct real_type;

template <typename T>
struct real_type<T, std::enable_if_t<std::is_arithmetic_v<T>>>
{ using type = T; };

template <typename T>
struct real_type<std::complex<T>, void>
{ using type = T; };

where you have a separate specialization for std::complex and, as observed by Patrick Roberts (thanks), without std::complex your is_arithmetic become a duplicate of std::is_arithmetic (so is better directly use std::is_arithmetic).

You get

real_type<int>                 r1;   // compile
real_type<std::complex<float>> r2; // compile
//real_type<std::string> r3; // compilation error

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 partial specialization without touching the primary template

I have bad news for you: what you want is impossible. If the primary template does not have an extra template parameter that defaults to void for the purpose of doing enable_if, there's just no way to do this in the most general sense. The closest you can come is to specialize the struct for a template class itself, in other words:

template <class T>
struct foo {};

template <class T>
struct S<foo<T>> {};

This will work. But obviously this does not yield the same flexibility as specializing something iff it matches a trait.

Your problem is actually exactly equivalent to the problem of trying to specialize std::hash for any types satisfying a trait. Like in your problem, the primary class template definition cannot be changed as it's in library code, and the library code actually uses specializations of hash automatically internally in certain situations, so one cannot really do with defining a new template class.

You can see a similar question here: Specializing std::hash to derived classes. Inheriting from a class is a good example of something that can be expressed as a trait, but not as a templated class. Some pretty knowledgeable C++ folk had eyes on that question, and nobody provided a better answer than the OP's solution of writing a macro to stamp out specialization automatically. I think that that will be the best solution for you too, unfortunately.

In retrospect, hash perhaps should have been declared with a second template parameter that defaulted to void to support this use case with minimal overhead in other cases. I could have sworn I even saw discussion about this somewhere but I'm not able to track it down. In the case of your library code, you might try to file an issue to have them change that. It does not seem to be a breaking change, that is:

template <class T, class = void>
struct S {};

template <>
struct S<double> {};

seems to be valid.

How does enable_if help select specializations of a class template?

From the reference on partial specialization of class templates:

When a class or variable (since C++14) template is instantiated, and there are partial specializations available, the compiler has to decide if the primary template is going to be used or one of its partial specializations.

If only one specialization matches the template arguments, that specialization is used

In this case, if the 2nd argument of the specialization is well-formed, it is chosen, precisely because it is a specialization, and not the primary template.

In case the 2nd template argument is not well-formed, then SFINAE kicks in. In particular:

When substituting the explicitly specified or deduced type for the template parameter fails, the specialization is discarded from the overload set instead of causing a compile error.

and

The following type errors are SFINAE errors:

attempting to use a member of a type, where
the type does not contain the specified member

How this is done, i.e. how exactly the compiler discards the specialization, instead of giving an error, is not specified; the compiler is just required to do the right thing.

Template specialization and selection in variadic template class

For specialization B, you need to ensure that the conditions to std::enable_if are orthogonal. In your version, if you supply v=12 both conditions v!=11 and v==12 yield true, meaning that both versions are enabled. That is the reason why you get the ambiguous instantiation error. The following compiles fine (https://godbolt.org/z/csaTWMd9v):

#include <tuple>
#include <iostream>

template<int v,typename, typename...>
struct B;

template<int v, typename...Ts, typename...Us>
struct B<v,std::enable_if_t<v!=11>,std::tuple<Ts...>, Us...> {
void print() {
std::cout << "B: Tuple selected" << std::endl;
}
};

template<int v, typename T, typename...Us>
struct B<v,std::enable_if_t<v==11>,T, Us...> {
void print() {
std::cout << "B: one element selected" << std::endl;
}
};

int main()
{
struct B<12,void,std::tuple<int>> b;
b.print();
}

Update: As asked in the comments, a way to check if a certain template parameter is a tuple of any kind can be done as follows (also compare e.g. this post). Is this closer what you want to achieve? (https://godbolt.org/z/dq85fM7KK)

#include <tuple>
#include <iostream>

template <typename>
struct is_tuple : std::false_type
{ };

template <typename... T>
struct is_tuple<std::tuple<T...>> : std::true_type
{ };

template<int v,typename, typename...>
struct B;

template<int v, typename...Ts, typename...Us>
struct B<v, std::enable_if_t<v!=11>, std::tuple<Ts...>, Us...> {
void print() {
std::cout << "B: Tuple selected" << std::endl;
}
};

template<int v, typename T, typename...Us>
struct B<v,std::enable_if_t<v==12 && !is_tuple<T>::value>, T, Us...> {
void print() {
std::cout << "B: one element selected" << std::endl;
}
};

int main()
{
B<12, void, std::tuple<int>>{}.print(); // Tuple selected
B<12, void, int>{}.print(); // one element selected
}


Related Topics



Leave a reply



Submit