Class template specialization partial ordering and function synthesis - c++

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

Related

SFINAE template specialization matching rule

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.

What is wrong with my application of SFINAE when trying to implement a type trait?

I needed a type trait that decays enums to their underlying type, and works the same as decay_t for all other types. I've written the following code, and apparently this is not how SFINAE works. But it is how I thought it should work, so what exactly is the problem with this code and what's the gap in my understanding of C++?
namespace detail {
template <typename T, std::enable_if_t<!std::is_enum_v<T>>* = nullptr>
struct BaseType {
using Type = std::decay_t<T>;
};
template <typename T, std::enable_if_t<std::is_enum_v<T>>* = nullptr>
struct BaseType {
using Type = std::underlying_type_t<T>;
};
}
template <class T>
using base_type_t = typename detail::BaseType<T>::Type;
The error in MSVC is completely unintelligible:
'detail::BaseType': template parameter '__formal' is incompatible with
the declaration
In GCC it's a bit better - says that declarations of the second template parameter are incompatible between the two BaseType templates. But according to my understanding of SFINAE, only one should be visible at all for any given T and the other one should be malformed thanks to enable_if.
Godbolt link
SFINAE applied to class templates is primarily about choosing between partial specialisations of the template. The problem in your snippet is that there are no partial specialisations in sight. You define two primary class templates, with the same template name. That's a redefinition error.
To make it work, we should restructure the relationship between the trait implementations in such as way that they specialise the same template.
namespace detail {
template <typename T, typename = void> // Non specialised case
struct BaseType {
using Type = std::decay_t<T>;
};
template <typename T>
struct BaseType<T, std::enable_if_t<std::is_enum_v<T>>> {
using Type = std::underlying_type_t<T>;
};
}
template <class T>
using base_type_t = typename detail::BaseType<T>::Type;
The specialisation provides a void type for the second argument (just like the primary would be instantiated with). But because it does so in a "special way", partial ordering considers it more specialised. When substitution fails (we don't pass an enum), the primary becomes the fallback.
You can provide as many such specialisation as you want, so long as the second template argument is always void, and all specialisations have mutually exclusive conditions.
BaseType isn't being partial-specialized, you're just redeclaring it, and since the non-type parameter has a different type, the compilation fails. you might want to do
#include <type_traits>
namespace detail {
template <typename T, bool = std::is_enum_v<T>>
struct BaseType;
template <typename T>
struct BaseType<T, false> {
using Type = std::decay_t<T>;
};
template <typename T>
struct BaseType<T, true> {
using Type = std::underlying_type_t<T>;
};
}
You declare the same struct with different parameter, which is forbidden.
You can do it with partial specialization:
namespace detail {
template <typename T, typename Enabler = void>
struct BaseType {
using Type = std::decay_t<T>;
};
template <typename E>
struct BaseType<E, std::enable_if_t<std::is_enum_v<E>>>
{
using Type = std::underlying_type_t<E>;
};
}
Demo

Specialization of non-type template argument

I have a struct
template <auto& t>
struct Foo {
using Type = decltype(t);
};
I also have a template class:
template <typename T> class MyClass {};
I want to create a specialization for this struct for any arg of type MyClass:
template <typename T>
struct Foo <MyClass<T>& t> {
using Type = int;
};
I'd Like to be able to use this class like:
Foo<true>::Type t = false;
This code doesn't compile. How can I do this kind of specialization? Is there some other approach using std::enable_if that I can use to accomplish this?
You can see the code at https://onlinegdb.com/1Qzum1Fs2J
Your code is near by the needed solution. The specialization simply needs a bit different syntax:
template <typename T> class MyClass {};
template < auto value >
struct Foo
{
void Check() { std::cout << "Default" << std::endl; }
};
template <typename T, MyClass<T> value>
struct Foo<value>
{
void Check() { std::cout << "Spezial" << std::endl; }
};
int main()
{
Foo<10> fi;
Foo<MyClass<int>{}> fm;
fi.Check();
fm.Check();
}
For gcc it needs trunk version. gcc 11 compiles but delivers wrong result!
See it working: Works on gcc trunk, clang trunk and msvc v19.30
As per C++20 and P0732R2 (Class Types in Non-Type Template Parameters) non-type template parameters may be of class type, allowing you to use partial specialization to specialize over non-type template arguments that are more specialized than the "any non-type template" argument of the primary template.
#include <type_traits>
// some class types
template<typename T> struct S{};
struct U {};
template<auto> // _any_ non-type template parameter
struct is_using_primary_template : std::true_type {};
// partial specialization for specialization of a class template type.
template<typename T, S<T> s>
struct is_using_primary_template<s> : std::false_type {};
// explicit specialization for some class type values.
constexpr S<void> sv{};
constexpr U u{};
template<>
struct is_using_primary_template<sv> : std::false_type {};
template<>
struct is_using_primary_template<u> : std::false_type {};
template<auto v>
constexpr bool is_using_primary_template_v{is_using_primary_template<v>::value};
static_assert(is_using_primary_template_v<0>);
static_assert(!is_using_primary_template_v<S<int>{}>); // partial spec.
static_assert(!is_using_primary_template_v<S<char>{}>); // partial spec.
static_assert(!is_using_primary_template_v<S<void>{}>); // explicit spec.
static_assert(!is_using_primary_template_v<sv>); // explicit spec.
static_assert(!is_using_primary_template_v<u>); // explicit spec.
Note that this is not any special case of partial specialization, but just typical use cases where the minimum requirement that all template parameters of each partial specialization are deducible is fulfilled.
That GCC 11 rejects this code is a bug (Bug 99699), which seems to have been (silently or via another bug report?) fixed in GCC 12/trunk. See the Q&A Partial specialization of templates over non-type literal parameters in C++20: clang and gcc disagree for details.
For how to apply this to your particular example, see #Klaus' answer

How do template aliases affect template parameter deduction?

In C++03, template parameter deduction does not occur in some contexts. For example:
template <typename T> struct B {};
template <typename T>
struct A
{
typedef B<T> type;
};
template <typename T>
void f(typename A<T>::type);
int main()
{
B<int> b;
f(b); // ERROR: no match
}
Here, int is not deduced for T, because a nested type such as A<T>::type is a non-deduced context.
Had I written the function like this:
template <typename T> struct B {};
template <typename T>
void f(B<T>);
int main()
{
B<int> b;
f(b);
}
everything is fine because B<T> is a deduced context.
In C++11, however, template aliases can be used to disguise a nested type in syntax similar to the second example. For example:
template <typename T> struct B {};
template <typename T>
struct A
{
typedef B<T> type;
};
template <typename T>
using C = typename A<T>::type;
template <typename T>
void f(C<T>);
int main()
{
B<int> b;
f(b);
}
Would template argument deduction work in this case? In other words, are template aliases a deduced context or a non-deduced context? Or do they inherit the deduced/non-deduced status of whatever they alias?
In other words, are template aliases a deduced context or a non-deduced context?
They are as deducible as the equivalent code without using template aliases. For example
template<typename T>
using ref = T&;
template<typename T>
void f(ref<T> r);
Now you can call f(x) and T will be deduced perfectly fine. At the definition time of f already, ref<T> is replaced by type T&. And T& is a deduced context.
In your case C<T> is replaced by typename A<T>::type, and that is a non-deduced context for T, so T cannot be deduced.
Imagine this:
template <typename T> struct Foo { typedef T type; }
template <> struct Foo<char> { typedef int type; }
template <typename T> using mytype = typename Foo<T>::type;
template <typename T> void f(mytype<T>);
Now if I want int n; f(n);, how could I decide whether I want T = int or T = char? The whole problem, which is unaffected by template aliases, is that you cannot deduce backwards to all the things that could possibly define something.
I think the relevant quote in the C++ standard is 14.5.7 [temp.alias] paragraph 2:
When a template-id refers to the specialization of an alias template, it is equivalent to the associated type obtained by substitution of its template-arguments for the template-parameters in the type-id of the alias template. [ Note: An alias template name is never deduced. — end note ]
There is an example following the quote which effectively spells out that it is pointless to use an alias template in a function template and hoping to deduce the template argument. This apparently applies even for situation which don't involve nested types.

class template partial specialization parametrized on member function return type

The following code, which attempts to specialize class template 'special', based on the return type of member function pointer types, results in a compile error with VC9:
template<class F> struct special {};
template<class C> struct special<void(C::*)()> {};
template<class R, class C> struct special<R(C::*)()> {};
struct s {};
int main()
{
special<void(s::*)()> instance;
return 0;
}
error C2752: 'special' : more than one partial specialization matches the template argument list
The same code is accepted by GCC-4.3.4, as shown by: http://ideone.com/ekWGg
Is this a bug in VC9 and if so, has this bug persisted into VC10?
I have however come up with a horrendously intrusive workaround (for this specific use case, at least. More general solutions welcome):
#include <boost/function_types/result_type.hpp>
#include <boost/type_traits/is_same.hpp>
template<typename F, typename R>
struct is_result_same :
boost::is_same<
typename boost::function_types::result_type<F>::type,
R
>
{};
template<class F, bool = is_result_same<F, void>::value>
struct special {};
template<class R, class C> struct special<R(C::*)(), true> {};
template<class R, class C> struct special<R(C::*)(), false> {};
This is a bug.
template <class C> struct special<void(C::*)()>; // specialization 1
template <class R, class C> struct special<R(C::*)()>; // specialization 2
According to 14.5.4.2, the partial ordering of these two class template specializations are the same as the partial ordering of these imaginary function templates:
template <class C> void f(special<void(C::*)()>); // func-template 3
template <class R, class C> void f(special<R(C::*)()>); // func-template 4
According to 14.5.5.2, the partial ordering of these two function templates is determined by substituting invented types for each type template parameter in the argument list of one and attempting template argument deduction using that argument list in the other function template.
// Rewrite the function templates with different names -
// template argument deduction does not involve overload resolution.
template <class C> void f3(special<void(C::*)()>);
template <class R, class C> void f4(special<R(C::*)()>);
struct ty5 {}; struct ty6 {}; struct ty7 {};
typedef special<void(ty5::*)()> arg3;
typedef special<ty6 (ty7::*)()> arg4;
// compiler internally tests whether these are well-formed and
// the resulting parameter conversion sequences are "exact":
f3(arg4());
f4(arg3());
The details of template argument deduction are in 14.8.2. Among the valid deductions are from template_name<dependent_type> and dependent_type1 (dependent_type2::*)(arg_list). So the f4(arg3()) deduction succeeds, deducing f4<void,ty5>(arg3());. The f3(arg4()) deduction can obviously never succeed, since void and ty6 do not unify.
Therefore function template 3 is more specialized than function template 4. And class template specialization 1 is more specialized than class template specialization 2. So although special<void(s::*)()> matches both specializations, it unambiguously instantiates specialization 1.