SFINAE template specialization matching rule - c++

I'm learning about SFINE with class/struct template specialization and I'm a bit confused by the matching rule in a nuanced example.
#include <iostream>
template <typename T, typename = void>
struct Foo{
void foo(T t)
{
std::cout << "general";
}
};
template <typename T>
struct Foo<T, typename std::enable_if<std::is_integral<T>::value>::type>
{
void foo(T t)
{
std::cout << "specialized";
}
};
int main()
{
Foo<int>().foo(3);
return 0;
}
This behaves as expected and prints out specialized, however if I change the specialized function slightly to the following:
template <typename T>
struct Foo<T, typename std::enable_if<std::is_integral<T>::value, int>::type>
{
void foo(T t)
{
std::cout << "specialized";
}
};
then it prints out general. What's changed in the second implementation that makes it less 'specialized'?

Re-focus your eyeballs a few lines higher, to this part:
template <typename T, typename = void>
struct Foo{
This means that when this template gets invoked here:
Foo<int>().foo(3);
This ends up invoking the following template: Foo<int, void>. After all, that's what the 2nd template parameter is, by default.
The 2nd template parameter is not some trifle, minor detail that gets swept under the rug. When invoking a template all of its parameters must be specified or deduced. And if not, if they have a default value, that rescues the day.
SFINAE frequently takes advantage of default template parameters. Now, let's revisit your proposed template revision:
template <typename T>
struct Foo<T, typename std::enable_if<std::is_integral<T>::value, int>::type>
The effect of this specialization is that the 2nd template parameter in the specialization is int, not void.
But Foo<int> is going to still use the default void for the 2nd template parameter because, well, that's the default value of the 2nd template parameter. So, only the general definition will match, and not any specialization.

It's not that it's less specialized, it no longer matches the template instantiation. The second type parameter defaults to void, but since the std::enable_if now aliases int, the specialization no longer matches.

Related

std::enable_if_t doesn't match partial specialization

If my understanding is correct, alias templates are actually templates and hence can be passed as arguments where a class template is expected. Which is what makes the following code valid -
template <template <bool, typename> T>
struct foo {};
foo<std::enable_if_t> x;
It is also possible to use partial specialization to match template classes. Which is why the following code prints "Version 2".
template <typename T>
struct foo {
static void print() {std::cout << "Version 1" << std::endl;}
};
template <template <typename> typename T, typename X>
struct foo<T<X>> {
static void print() {std::cout << "Version 2" << std::endl;}
};
template <typename T>
struct bar {
};
...
foo<bar<int>>::print();
Now my question is, why doesn't the following code print "Version 2", but instead prints "Version 1"
template <typename T>
struct foo {
static void print() {std::cout << "Version 1" << std::endl;}
};
template <template <bool, typename> typename T, bool X, typename Y>
struct foo<T<X, Y>> {
static void print() {std::cout << "Version 2" << std::endl;}
};
foo<std::enable_if_t<true, int>>::print();
Shouldn't the second partial specialization be a better match where T=std::enable_if_t, X=true, Y=int?
As expected, the following prints "Version 2"
foo<std::enable_if<true, int>>::print();
In your example however, the type std::enable_if_t<true, int> is equivalent to int and cannot match cannot match T<X, Y>.
cppreference explains it like this:
An alias template is a template which, when specialized, is equivalent to the result of substituting the template arguments of the alias template for the template parameters in the type-id
The alias std::enable_if_t can match template <bool, typename> typename, but once specialized it cannot be deconstructed.
Whenever an alias template has arguments, it is immediately replaced with the type it aliases. So, for example, std::enable_if_t<true, int> is immediately replaced by int. This applies even in a dependent context; so for example if you have a function like this:
template <bool b, typename T>
void foo(std::enable_if_t<b, T>);
the compiler rewrites it to:
template <bool b, typename T>
void foo(typename std::enable_if<b, T>::type);
The original alias has disappeared from the equation.
It follows that a type like T<X, Y> can't be used to deduce T as an alias template because whatever type U is provided for T<X, Y> to match against, if it originally had the form A<X, Y> for some alias template A, it would have been replaced by whatever that alias template aliases to, and A would no longer be present. (However, an alias template can be deduced as an argument of a class template that has a template template parameter, since an alias template can be a valid template template argument)

template template variadic parameter pack

Can someone please explain the below code with template parameter packs.
How does it work? How are the template parameters packed and unpacked in this case:
template<typename Test, template<typename...> class Ref> //#6
struct is_specialization : std::false_type {};
template<template<typename...> class Ref, typename... Args> //#7
struct is_specialization<Ref<Args...>, Ref>: std::true_type {};
Possible requested usage (inspired by Function Template Overloading or Specialization for inner template type std::vector<std::vector<T>>)
template <typename T>
bool f(T& x) // #1
{
std::cout << "body of f\n";
return f(x);
}
template <typename T>
bool f(std::vector<T>& v) // #2
{
std::cout << "body of f for vectors\n";
return true;
}
template<typename T>
typename std::enable_if<
is_specialization<typename T::value, std::vector>::value, T>::type
bool f(std::vector<T>& v) // #5
{
std::cout << "body of f for vectors<vectors>\n";
return true;
}
int main() {
std::vector<int> v{1,2}
f(v);
}
Below are some explanations on variadic templates syntax, packing and unpacking -- on the specific code in question and how to make it work 1.
It seems that what you want to achieve is to differentiate between std::vector<int> and std::vector<float>.
However
Your function #1 is too greedy and would take all possible arguments:
template <typename T>
bool f(T& x) // #1
{
std::cout << "body of f\n";
return f(x);
}
which would result with ambiguity if any call fits also one of the overloaded versions.
So, we first need to:
Separate between is_vector or not
We can achieve that with the following code:
// [A]
template <class, template <class...> class>
struct is_of_template_type : std::false_type {};
// [B]
template <class T, class... Args, template <class...> class U>
struct is_of_template_type<U<T, Args...>, U> : std::true_type {};
// [C]
template <class Something>
struct is_vector: is_of_template_type<Something, std::vector> {};
[A] is the base template (nothing to do with inheritance) for the generic case, before any specialization, for allowing to test if a given type is a specific template. This template arguments are: (a) some type (b) some other type that must be a template, with some unknown template arguments.
[B] is the specialization for the true case. The caller should provide two template parameters but it would fit this specialization only if the first template parameter fits the template type given as the second template parameter. Note that the expression expects two template parameters: (a) a template argument U<T, Args...> from which we will infer the types T and Args, and (b) another template argument -- which must be a template argument because of the base template -- for which we ignore the inner template arguments, as we just need the first type to match the second, regardless of the inner template arguments.
[C] is the specific usage for checking if a given type is a vector, without the need to deal with the vector template parameters.
Now we can rewrite function #1 to:
template<typename Something>
typename std::enable_if<!is_vector<Something>::value>::type
f(const Something& v) // #1
{
std::cout << "body of f for generic Something\n";
}
and it is not so greedy as before, as it takes only non-vectors.
Now we are ready for our next task:
Separate between different kind of vectors, i.e. is_vector_of_T
To achieve that we would add the following:
template <typename Container, typename T>
struct is_vector_of_T: std::false_type {};
template <typename T>
struct is_vector_of_T<std::vector<T>, T>: std::true_type {};
and now we can have separate functions for std::vector<int> and std::vector<float>:
template<typename Something>
typename std::enable_if<is_vector_of_T<Something, int>::value>::type
f(const Something& v) // #2
{
std::cout << "body of f for vector<int>\n";
}
template<typename Something>
typename std::enable_if<is_vector_of_T<Something, float>::value>::type
f(const Something& v) // #3
{
std::cout << "body of f for vector<float>\n";
}
Can we use it to isolate std::vector<std::vector<int>>?
Yes we can:
template<typename Something>
typename std::enable_if<is_vector_of_T<Something, std::vector<int>>::value>::type
f(const Something& v) // #4
{
std::cout << "body of f for vector<vector<int>>\n";
}
template<typename Something>
typename std::enable_if<is_vector_of_T<Something, std::vector<float>>::value>::type
f(const Something& v) // #5
{
std::cout << "body of f for vector<vector<float>>\n";
}
Code:
https://godbolt.org/z/EFeGZk
Notes:
I use enable_if in all cases above to declare the return value of the method as void or as non-existing (SFINAE), this is a common use
we may consider instead of overloading template functions to specialize template classes, it may reduce the need of enable_if
with C++20 we would replace the use of enable_if with the requires syntax
Other relevant SO questions:
How to tell if template type is an instance of a template class?
Determine if a type is an STL container at compile time
1
If variadic templates packing and unpacking is entirely new to you I would suggest starting to learn this topic from some more basic examples like this or this.
The question is specifically related to template template parameter (the duplicate template is not a mistake) which is a more advanced topic, you can follow this as a good reference.
Then, the question is more specifically related to variadic template template parameter, related to examples like this and this.

Class template specialization partial ordering and function synthesis

The rules for picking which class template specialization is preferred involve rewriting the specializations into function templates and determining which function template is more specialized via the ordering rules for function templates [temp.class.order]. Consider this example, then:
#include <iostream>
template <class T> struct voider { using type = void; };
template <class T> using void_t = typename voider<T>::type;
template <class T, class U> struct A { };
template <class T> int foo(A<T, void_t<T>> ) { return 1; }
template <class T> int foo(A<T*, void> ) { return 2; }
int main() {
std::cout << foo(A<int*, void>{});
}
Both gcc and clang print 2 here. This makes sense with some previous examples - deducing against a non-deduced context (void against void_t<T>) is just ignored, so deducing <T, void_t<T>> against <X*, void> succeeds but deducing <T*, void> against <Y, void_t<Y>> fails in both arguments. Fine.
Now consider this generalization:
#include <iostream>
template <class T> struct voider { using type = void; };
template <class T> using void_t = typename voider<T>::type;
template <int I> struct int_ { static constexpr int value = I; };
template <class T, class U> struct A : int_<0> { };
template <class T> struct A<T, void_t<T>> : int_<1> { };
template <class T> struct A<T*, void> : int_<2> { };
int main() {
std::cout << A<int*, void>::value << '\n';
}
Both clang and gcc report this specialization as ambiguous, between 1 and 2. But why? The synthesized function templates aren't ambiguous. What's the difference between these two cases?
Clang is being GCC-compatible (and compatible with existing code that depends on both of these behaviors).
Consider [temp.deduct.type]p1:
[...] an attempt is made to find template argument values (a type for a type parameter, a value for a non-type parameter, or a template for a template parameter) that will make P, after substitution of the deduced values (call it the deduced A), compatible with A.
The crux of the issue is what "compatible" means here.
When partially ordering function templates, Clang merely deduces in both directions; if deduction succeeds in one direction but not the other, it assumes that means the result will be "compatible", and uses that as the ordering result.
When partially ordering class template partial specializations, however, Clang interprets "compatible" as meaning "the same". Therefore it only considers one partial specialization to be more specialized than another if substituting the deduced arguments from one of them into the other would reproduce the original partial specialization.
Changing either of these two to match the other breaks substantial amounts of real code. :(

Member function template selection and SFINAE

I've been trying to understand the way C++ selects templates. Namely, consider the following code sample:
template <typename R>
class Curious
{
public:
template <typename T, typename std::enable_if<std::is_const<T>::value, int>::type = 33>
void test1() {}
template <typename T, typename std::enable_if<!std::is_const<T>::value, int>::type = 33>
void test1() {}
template <typename T, typename = typename std::enable_if<std::is_const<T>::value>::type>
void test2() {}
template <typename T, typename = typename std::enable_if<!std::is_const<T>::value>::type>
void test2() {}
template <typename std::enable_if<std::is_const<R>::value>::type * = nullptr>
void test3() {}
template <typename std::enable_if<!std::is_const<R>::value>::type * = nullptr>
void test3() {}
// works
template <typename T = void>
typename std::enable_if<std::is_const<R>::value, T>::type test4() {}
template <typename T = void>
typename std::enable_if<!std::is_const<R>::value, T>::type test4() {}
// also works
template <typename T = void, typename std::enable_if<std::is_const<R>::value, T>::type * = nullptr>
void test5() {}
template <typename T = void, typename std::enable_if<!std::is_const<R>::value, T>::type * = nullptr>
void test5() {}
}; // Curious
The first two functions (test1) work fine (why?):
Curious<int> curious;
curious.test1<int>();
curious.test1<const int>();
While the rest of them cause compilation errors.
Regarding the function test2 the compiler claims I'm trying to create a duplicate:
error C2535: 'void Curious::test2(void)': member function already defined or declared
Here the documentation says:
A common mistake is to declare two function templates that differ only
in their default template arguments. This is illegal because default
template arguments are not part of function template's signature, and
declaring two different function templates with the same signature is
illegal.
So it seems to be the case. However, I don't see that much difference from the first two functions, which also have the default template argument. Thus we have a default type (test2 - doesn't work) against a default value (test1 - works). Is there any rule about it?
In case of test3: error C2039: 'type': is not a member of 'std::enable_if'
Like in the first case this time the member function template has a default non-type parameter, but it depends on the class template parameter. Now SFINAE doesn't skip the wrong one (also not sure why).
In the fourth case SFINAE resolves the template by the return type. But don't these test4 functions have identical signature? As they differ only in the return type.
As far as I understand, in the fifth case adding extra parameter makes test5 signature dependent on the function template parameter, therefore SFINAE kicks in and resolution works.
I'm quite confused about how C++ deals with these templates. Could somebody be so kind to clear these things up?
With default value removed, for test1, you have:
template <typename T, typename std::enable_if<std::is_const<T>::value, int>::type>
void test1();
template <typename T, typename std::enable_if<!std::is_const<T>::value, int>::type>
void test1();
Which have clearly different signatures.
For test2:
template <typename T, typename> void test2();
template <typename T, typename> void test2();
Which are clearly identical signatures.
For test3, SFINAE doesn't apply as you have hard error as R is fixed in the class and your enable_if doesn't depend of template parameter of the function.
For test4, there is an exception about signature for template function as overload may differ only by return type so
int foo();
char foo(); // Illegal.
but
template <typename T> int foo();
template <typename T> char foo(); // legal, even it is not trivial to call
In addition, std::enable_if<!std::is_const<R>::value, T>::type depends on template parameter T so it is ok.
For test5, second template parameter depends on first template parameter T, so it is ok too.

Partial Specialization Question

I need a fresh pair of eyes.
This is obviously illegal, but it shows what I'm trying to do:
template <typename T, T> struct Foo
{
};
template <typename T> struct Foo <T, 0> //Obviously I can't do this.
{
};
Is there any way to wrap T or do something tricky so that this sort of thing can work?
Thanks!
Yes, you can use this trick:
template <typename T, T, T=0> struct Foo {
};
template <typename T, T t> struct Foo <T, t, t> {
};
If t is 0 in the specialization, it will match the default argument, and the specialization is taken. Otherwise, the primary template is taken.
Edit: What the heck does the third parameter mean? Well, it's a default and it's 0. It will be passed when we name the specialization Foo<int, 5> for example. But really, we instantiate a template with the arguments Foo<int, 5, 0>, because the last is a default argument. The partial specialization matches, when the third parameter matches the third argument, which is zero by default, and if the third and second arguments are the same, because both are t.
The above trick has the drawback that also Foo<int, 9, 9> uses our specialization. But on the other side, the above is remarkable simple, so that you can probably get away with that. If you don't want that to work, then you can use enable_if, which is a bit more complicated:
template <typename T, T, typename = void> struct Foo {
};
template <typename T, T t>
struct Foo <T, t, typename boost::enable_if_c< t == 0 >::type> {
};
Now, even if you say Foo<int, 9, void>, our partial specialization won't be chosen, because the condition t == 0 isn't true, and ::type will thus not be available. SFINAE doesn't chose the specialization then. Of course, with this enable_if solution, you are not limited to t being zero. Any condition will do. For reference, here is the code of enable_if, if you don't use boost. Cut the _c suffix above then, which we don't need for the version below:
template<bool C, typename T = void>
struct enable_if {
typedef T type;
};
template<typename T>
struct enable_if<false, T> { };