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 thedecltype
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
Use of Typename Keyword with Template Function Parameters
Making My Own Photo-Mosaic App with Qt Using C++
How to Pass a C++ Lambda to a C-Callback That Expects a Function Pointer and a Context
How to Use Memcpy in C++ to Copy Classes That Have No Pointers or Virtual Functions
How to Have Polymorphic Containers with Value Semantics in C++
How to Convert Rgb -> Yuv -> Rgb (Both Ways)
Std::Enable_If:Parameter VS Template Parameter
Accessing Elements of a Cv::Mat with At<Float>(I, J). Is It (X,Y) or (Row,Col)
What's the Advantage of Using Std::Allocator Instead of New in C++
Opensouce C/C++ Math Expression Parser Library
How to Erase & Delete Pointers to Objects Stored in a Vector
C++ Implementing Timed Callback Function
What's the Time Complexity of Iterating Through a Std::Set/Std::Map
Why Does Unique_Ptr Take Two Template Parameters When Shared_Ptr Only Takes One