Sfinae and Partial Class Template Specializations

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.

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

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.

Partial Template Specialization SFINAE

Workaround could look as follows:

#include <type_traits>

template <bool...>
struct bool_pack { };

template <bool... Bs>
using var_and = std::is_same<bool_pack<true, Bs...>, bool_pack<Bs..., true>>;

template<typename... Ts>
struct type_list {};

template <typename T, typename Enable = void>
class foo;

template <typename... T>
class foo<type_list<T...>, std::enable_if_t<var_and<std::is_integral<T>::value...>::value>> {};

int main()
{
foo<type_list<int, int, int>> {};
}

[live demo]

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.

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

Partial Template Specialization using enable_if

In the declaration of Foo the second template parameter defaults to void. That means that the following variable:

Foo<MyEnum> v3;

is actually

Foo<MyEnum, void> v3;

Now the question is: does this correspond to the specialization you want? Not really, because in your specialization for enum:

  • std::is_enum<T>::value = true when T = MyEnum
  • std::enable_if<std::is_enum<T>::value, T>::type = T = MyEnum
    when T=MyEnum

So, for T=MyEnum, your give the specialization for Foo<T, T>, which does not match the variable above.

As noted in a comment, a simple fix is to declare the specialization as

class Foo<T, typename std::enable_if<std::is_enum<T>::value>::type>

In this way, the second template parameter is void when T=MyEnum, so it matches the variable declaration of v3.



Related Topics



Leave a reply



Submit