Why Must Specializing Argument be void? - c++

So yet another question in this saga. Guillaume Racicot has been good enough to provide me with yet another workaround so this is the code I'll be basing this question off of:
struct vec
{
double x;
double y;
double z;
};
namespace details
{
template <typename T>
using subscript_function = double(*)(const T&);
template <typename T>
constexpr double X(const T& param) { return param.x; }
template <typename T>
constexpr double Y(const T& param) { return param.y; }
template <typename T>
constexpr double Z(const T& param) { return param.z; }
}
template <typename T, typename = void>
constexpr details::subscript_function<T> my_temp[] = { &details::X<T>, &details::Y<T> };
template <typename T>
constexpr details::subscript_function<T> my_temp<T, enable_if_t<is_floating_point_v<decltype(details::X(T()))>, T>>[] = { &details::X<T>, &details::Y<T>, &details::Z<T> };
int main() {
vec foo = { 1.0, 2.0, 3.0 };
for(const auto i : my_temp<decltype(foo)>) {
cout << (*i)(foo) << endl;
}
}
The problem seems to arise in my specialization when I return something other than void. In the code above for example, enable_if_t<is_floating_point_v<decltype(details::X(T()))>, T> prevents specialization, while simply removing the last argument and allowing enable_if to return void allows specialization.
I think this points to my misunderstanding of what is really happening here. Why must the specialized type always be void for this to work?
Live Example

Not sure to understand what you don't understand but...
If you write
template <typename T, typename = void>
constexpr details::subscript_function<T> my_temp[] = { &details::X<T>, &details::Y<T> };
template <typename T>
constexpr details::subscript_function<T> my_temp<T, enable_if_t<is_floating_point_v<decltype(details::X(T()))>, T>>[] = { &details::X<T>, &details::Y<T>, &details::Z<T> };
you have a first, main, template variable with two templates: a type and a type with a default (void).
The second template variable is enabled when std::enable_if_t is void.
What's happen when you write
for(const auto i : my_temp<decltype(foo)>)
?
The compiler:
1) find my_temp<decltype(foo)> that has a single template parameter
2) look for a matching my_temp template variable
3) find only a my_temp with two template parameters but the second has a default, so
4) decide that my_temp<decltype(foo)> can be only my_temp<decltype(foo), void> (or my_temp<vec, void>, if you prefer)
5) see that the main my_temp matches
6) see that the my_temp specialization doesn't matches because
enable_if_t<is_floating_point_v<decltype(details::X(T()))>, T>
is T (that is vec), so could match only my_temp<vec, vec> that is different from my_temp<vec, void>.
7) choose the only template variable available: the main one.
If you want that the specialization is enabled by
enable_if_t<is_floating_point_v<decltype(details::X(T()))>, T>
you should use T
// ..............................V T! not void
template <typename T, typename = T>
constexpr details::subscript_function<T> my_temp[] = { &details::X<T>, &details::Y<T> };
as default for second template type in the main template variable.
Off Topic suggestion: better use std::declval inside the std::is_floating_point_v test; I suggest
std::enable_if_t<std::is_floating_point_v<decltype(details::X(std::declval<T>()))>>

How template specialization works:
There is a primary specialization. This one basically defines the arguments and defaults.
template <typename T, typename = void>
This is the template part of your primary specialization. It takes one type, then another type that defaults to void.
This is the "interface" of your template.
template <typename T>
[...] <T, enable_if_t<is_floating_point_v<decltype(details::X(T()))>, T>> [...]
here is a secondary specialization.
In this case, the template <typename T> is fundamentally different. In the primary specialization, it defined an interface; here, it defines "variables" that are used below.
Then we have the part where we do the pattern matching. This is after the name of the template (variable in this case). Reformatted for sanity:
<
T,
enable_if_t
<
is_floating_point_v
<
decltype
(
details::X(T())
)
>,
T
>
>
now we can see the structure. There are two arguments, matching the two arguments in the primary specialization.
The first one is T. Now, this matches the name in the primary specialization, but that means nothing. It is like calling a function make_point(int x, int y) with variables x,y -- it could be y,x or m,n and make_point doesn't care.
We introduced a completely new variable T in this specialization. Then we bound it to the first argument.
The second argument is complex. Complex enough that it is in a "non-deduced context". Typically, template specialization arguments are deduced from the arguments passed to template as defined in the primary specialization; non-deduced arguments are not.
If we do some_template< Foo >, matching a type T against Foo gets ... Foo. Pretty easy pattern match. Fancier pattern matches are permitted, like a specialization that takes a T*; this fails to match against some_template<int>, but matches against some_template<int*> with T=int.
Non-deduced arguments do not participate in this game. Instead, the arguments that do match are plugged in, and the resulting type is generated. And if and only if that matches the type passed to the template in that slot does the specialization match.
So lets examine what happens we pass vec as the first argument to my_temp
First we go to the primary specialization
template<typename T, typename=void>
my_temp
now my_temp<vec> has a default argument. It becomes my_temp<vec,void>.
We then examine each other specialization to see if any of them match; if none do, we stay as the primary specialization.
The other specialization is:
template<typename T>
[...] my_temp<
T,
enable_if_t
<
is_floating_point_v
<
decltype
(
details::X(T())
)
>,
T
>
>[...]
with [...] for stuff that doesn't matter.
Ok, the first argument is bound to T. Well, the first argument is vec, so that is easy. We substitute:
template<typename T>
[...] my_temp<
vec,
enable_if_t
<
is_floating_point_v
<
decltype
(
details::X(vec())
)
>,
vec
>
>[...]
then evaluate:
template<typename T>
[...] my_temp<
vec,
enable_if_t
<
is_floating_point_v
<
double
>,
vec
>
>[...]
and more:
template<typename T>
[...] my_temp<
vec,
enable_if_t
<
true,
vec
>
>[...]
and more:
template<typename T>
[...] my_temp<
vec,
vec
>[...]
ok, remember we where trying to match against my_temp<vec,void>. But this specialization evaluated to my_temp<vec,vec>, and those don't match. Rejected.
Remove the ,T from enable_if, or make it ,void (same thing), and the last line of the above argument becomes my_temp<vec,void> matches my_temp<vec,void>, and the secondary specialization is chosen over the primary one.
It is confusing. The same syntax means fundamentally different things in primary specialization and secondary ones. You have to understand pattern matching of template arguments and non-deduced contexts.
And what you usually get is someone using it like a magic black box that you copy.
The magic black box -- the patterns -- are useful because they mean you don't have to think about the details of how you got there. But understanding pattern matching of template arguments, deduced and non-deduced contexts, and the differences between primary and secondary specializations is key to get why the black box works.

With
struct vec
{
double x;
double y;
double z;
};
and
template <typename T>
constexpr double X(const T& param) { return param.x; }
we'll find out that
is_floating_point_v<decltype(details::X(T()))
evaluates to true (unless you're going to specialise X for vec not to return floating point...).
So we actually have:
template <typename T>
constexpr details::subscript_function<T> my_temp<T, enable_if_t<true, T>>[]
= { /*...*/ };
or shorter:
template <typename T>
constexpr details::subscript_function<T> my_temp<T, T>[]
= { /*...*/ };
(if it exists at all, of course...). Explicitly choosing one or the other:
my_temp<decltype(foo), void>
my_temp<decltype(foo), int>
my_temp<decltype(foo), double>
all match the main template, but none of the specialisation.
my_temp<decltype(foo), decltype(foo)>
now does match the specialisation (which exists because of X(foo) returning double...).
Finally back to my_temp<decltype(foo)> – well, only one template parameter given. Which is the type of the second one? The default parameter tells you (or better: the compiler), it is void. And according to above...
So if you want to match the specialisation, either this one needs void as type of second template parameter (as you discovered already) or you change the default in the non-specialized template to being equal to first template parameter (typename T, typename = T).
Actually, you could select any type for the default and the specialisation, as long as you choose the same for both (e. g. twice int, std::string, MyVeryComplexCustomClass, ...).

Related

Why is the concept in template template argument not verified?

C++20 allows the program to specify concept for template template argument. For example,
#include <concepts>
template <typename T> concept Char = std::same_as<T, char>;
template <typename> struct S {};
template <template <Char U> typename T, typename U> T<U> foo() { return {}; }
int main() { foo<S, int>(); }
the first template argument of the function foo is expected to be a single argument template.
The concept Char is defined to be true only the type char, so an attempt to satisfy it for int shall fail. Still above program is accepted by all compilers: https://gcc.godbolt.org/z/PaeETh6GP
Could you please explain, why the concept in template template argument can be specified, but it will be still ignored?
A template (actual) argument matches a template (formal) parameter if the latter is at least as specialised as the former.
template <Char> typename T is more specialised than template <typename> struct S. Roughly speaking, template <Char> accepts a subset of what template <typename> accepts (the exact definition of what "at least as specialised" actually means is rather involved, but this is a zeroth approximation).
This means that the actual argument can be used in all contexts where the formal parameter can be used. That is, for any type K for which T<K> is valid, S<K> is also valid (because S<K> is valid for any K).
So it is OK to substitute S for T.
If you do it the other way around:
template<typename T> concept Any = true;
template<typename T> concept Char = Any<T> && std::same_as<T, char>;
template<template<Any> class T> void foo();
template<Char> struct S { };
int main()
{
foo<S>();
}
then this is ill-formed, because (roughly) you can say T<int> but not S<int>. So S is not a valid substitute for T.
Notes:
Why would we need this always-true concept Any? What's wrong with simply saying template <template <typename> typename>? Well, that's because of a special rule: if the parameter is not constrained at all, constraints of the argument are ignored, everything goes.
Why write Any<T> && std::same_as<T, char>;? To illustrate a point. The actual rules do not evaluate the boolean values of constraints, but compare constraints as formulae where atomic constraints serve as variables, see here. So the formal reason is that S has a conjunction of a strictly larger (inclusion-wise) set of atomic constraints than T. If S had the same or a strictly smaller set, it would be well-formed. If two sets are not ordered by inclusion, then neither template is more specialised, and there is no match.

disable template member function if return type is an array

https://www.godbolt.org/z/_4aqsF:
template <typename T> struct Container
{
template <typename TPred> T find_if(TPred pred); // the culprit
};
template <typename T> Container<T> MakeContainer(T const &)
{
return Container<T>();
}
int main()
{
auto x = MakeContainer("Hello!");
}
gcc, clang and msvc apparently agree that this cannot compile because find_if would return an array.
(I would have assumed that the member template isn't instantiated since it doesn't get used - apparently, this simplistic view is wrong.)
Why does SFINAE not apply here?
Is there a way to exclude the member template for types where T is not a returnable type?
SFINAE is not in play because the members of the types produced in your MakeContainer return point are not examined during SFINAE of MakeContainer overloads.
SFINAE happens only in an immediate context. Bodies of types and functions are not in-scope and do not cause a subsitution failure.
template <typename T=char[7]> Container<char[7]> MakeContainer(char const (&)[7])
this signature is fine.
Once selected, the Container<char[7]> is instantiated and its methods parsed.
template <typename TPred> char[7] find_if(TPred pred); // the culprit
there is no TPred that could cause this find_if to be a valid method, so your program is ill formed no diagnostic required.
The correct fix is:
template <typename T> struct Container
{
template <typename TPred> T find_if(TPred pred); // the culprit
};
template <class T, std::size_t N> struct Container<T[N]>:
Container<std::array<T,N>>
{
using Container<std::array<T,N>>::Container;
};
of course, Container<std::array<T,N>> itself needs a very special find_if and probably constructors. But at least it doesn't break immediately.
SFINAE removes from overload sets the overloads which would be illegal during template argument deduction.
Here, the overload set contains only one candidate: MakeContainer<const char (&)[7]>. Template argument deduction ends here. No ambiguity. Everything's fine.
Then, the type Container<const char (&)[7]> is instantiated. And it produces a templated function (Container<const char (&)[7]>::find_if) whose signatures are illegal (all of them, since T is deduced in the context of find_if). SFINAE is not at play.
Now, you can add some SFINAE to your container's find_if function by making its return type depend on its template argument. For that, see max66's answer for that.
To use SFINAE on fidn_if you need to use dependent parameters of the function itself, here's a version that SFINAE on non-returnable types:
template <typename TPred, class U = T, typename std::enable_if<
std::is_same<T, U>::value
&& !std::is_abstract<U>::value
&& !std::is_function<U>::value
&& !std::is_array<U>::value
, bool>::type = true>
U find_if(TPred pred);
Try with
template <typename TPred, typename U = T>
U find_if (TPred pred); // the culprit
SFINAE, over methods, doesn't works with templates parameters of the class. Works over templates of the method itself. So you have to make the SFINAE substitution dependant from a template parameter of the method itself.
So not T but U.
If you fear that someone can "hijack" your function explicating the template types as follows
auto x = MakeContainer("Hello!");
x.find_if<int, int>(1);
you can impose that U and T are the same type
template <typename TPred, typename U = T>
typename std::enable_if<std::is_same<U, T>::value, U>::type
find_if (TPred pred) // the culprit

When specializing a class, how can I take a different number of template parameters?

I just asked this question: Can I get the Owning Object of a Member Function Template Parameter? and Yakk - Adam Nevraumont's answer had the code:
template<class T>
struct get_memfun_class;
template<class R, class T, class...Args>
struct get_memfun_class<R(T::*)(Args...)> {
using type=T;
};
These is clearly an initial declaration and then a specialization of struct get_memfun_class. But I find myself uncertain: Can specializations have a different number of template parameters?
For example, is something like this legal?
template<typename T>
void foo(const T&);
template<typename K, typename V>
void foo<pair<K, V>>(const pair<K, V>&);
Are there no requirements that specializations must take the same number of parameters?
You seem to confuse the template parameters of the explicit specialization and the template arguments you use to specialize the template.
template<class T> // one argument
struct get_memfun_class; // get_memfun_class takes one template (type) argument
template<class R, class T, class...Args>
struct get_memfun_class<R(T::*)(Args...)> {
// ^^^^^^^^^^^^^^^^
// one type argument
using type=T;
}; // explicit specialization takes one template argument
Yes, there are three template parameters for the explicit specialization, but that doesn't mean that the explicit specialization takes three arguments. They are there to be deduced. You can form a single type using multiple type parameters, which is what is happening there. Also consider that you can fully specialize a template:
template <>
struct get_memfun_class<void>;
// ^^^^
// one type argument
Here it's the same thing. Yes, the explicit specialization takes no parameters, but that just means that there is none to be deduced and indeed you are explicitly writing a template parameter (void) and so the amount of template arguments of the specialization match those of the primary template.
Your example is invalid because you cannot partially specialize functions.
Are there no requirements that specializations must take the same number of parameters?
There is; and is satisfied in your example.
When you write
template<class T>
struct get_memfun_class;
you say that get_mumfun_class is a template struct with a single template typename argument; and when you write
template<class R, class T, class...Args>
struct get_memfun_class<R(T::*)(Args...)> {
using type=T;
};
you define a specialization that receive a single template typename argument in the form R(T::*)(Args...).
From the single type R(T::*)(Args...), you can deduce more that one template paramenters (R, T and the variadic Args..., in this example) but the type R(T::*)(Args...) (a method of a class that receive a variadic list of arguments) remain one.
For example, is something like this legal?
template<typename T>
void foo(const T&);
template<typename K, typename V>
void foo<pair<K, V>>(const pair<K, V>&);
No, but (as written in comments) the second one isn't a class/struct partial specialization (where std::pair<K, V> remain a single type), that is legal; it's a template function partial specialization that is forbidden.
But you can full specialize a template function; so it's legal (by example)
template<>
void foo<std::pair<long, std::string>(const std::pair<long, std::string>&);
as is legal the full specialization for get_memfun_class (to make another example)
template<>
struct get_memfun_class<std::pair<long, std::string>> {
using type=long long;
};

Template specialisation with default argument [duplicate]

This question already has answers here:
How does `void_t` work
(3 answers)
Closed 4 years ago.
I have a program that is as follows. There is a base template struct X and a partial specialisation with SFINAE.
template <typename T, typename U = void>
struct X{
X() {
std::cout << "in 1" << std::endl;
};
};
template <typename T>
struct X< T, std::enable_if_t<std::is_integral_v<T>> > {
X() {
std::cout << "in 2" << std::endl;
};
};
int main() {
X<int> x;
}
When running the program in 2 is printed.
Why is it that the second specialization is chosen over the first since both of them effectively declare a struct X<int, void>. What makes std::enable_if_t<std::is_integral_v<T>> more specialized than a default template type argument as shown in the base template?
Why does the default type argument of the base template have to be the same as the type defined by the partial specialization for the partial specialization to be called and in 2 to be printed.
Why does changing to std::enable_if_t<std::is_integral_v<T>, bool> cause the base template in 1 to be called?
The answers to your questions lie in Template Partial Ordering. This is the mechanism the compiler uses to determine which template is the best fit (be it a function template overload, or in your case, a class template specialization).
In brief, your generic template implementation has 2 parameters T and U, whereas your SFINAE specialization have only the T parameter, while the second is deduced from T. It is therefore more specialized than the general case and in the end, when you refer to X<int, void>, the specialization is chosen.
Now question 2. Suppose we replace the enable_if parameter with bool instead of void. Now our specialization will be X<int, bool> instead of X<int, void>, so when you refer to X<int>, i.e. X<int, void>, it doesn't match the specialization anymore because those are 2 different types.
1) [...] What makes std::enable_if_t> more specialised than a default template type argument as shown in the base template?
So do you know that, if two template match, the more specialized is selected.
Well... the second one is more specialized because if X matches the specialization (so if X is an integral type), it's that matches also the generic version.
But exist couples of types (by example: std::string, void) that matches the generic version and doesn't matches the specialization.
So the specialization is more specialized that the generic version, so is preferred when both template match.
Why does the default type argument of the base template have to be the same as the type defined by the partial specialisation for the partial specialisation to be called and in 2 to be printed. Why does changing to std::enable_if_t, bool> cause the base template in 1 to be called?
You have to understand how works the trick of the default type value.
You have that the generic version that is
template <typename T, typename U = void>
struct X;
so writing X<int> x, you're writing X<int, void> x; and surely matches the generic version.
The specialization is
template <typename T>
struct X< T, std::enable_if_t<std::is_integral_v<T>>>;
Question: X<int, void> matches X< T, std::enable_if_t<std::is_integral_v<T>>> ?
Answer: yes, because int is integral, so std::enable_if_t<std::is_integral_v<T>> is substituted with void.
Suppose now that the generic specialization become
template <typename T>
struct X< T, std::enable_if_t<std::is_integral_v<T>, bool>>
We have that X<int> x is again X<int, void> x and matches again the generic version.
Question: X<int, void> matches also X< T, std::enable_if_t<std::is_integral_v<T>, bool>> ?
Answer: no, because std::enable_if_t<std::is_integral_v<T>, bool> become bool and X<int, void> doesn't matches X<int, bool>
So the generic version is the only one that matches and is selected.

Universal reference with templated class

Example:
template <typename T>
class Bar
{
public:
void foo(T&& arg)
{
std::forward<T>(arg);
}
};
Bar<int> bar;
bar.foo(10); // works
int a{ 10 };
bar.foo(a); // error C2664: cannot convert argument 1 from 'int' to 'int &&'
It seems that universal references works only with templated functions and only with type deduction, right? So it make no sense to use it with class? And does using of std::forward makes sense in my case?
Note that the preferred terminology (i.e. the one which will be in future versions of the spec) is now forwarding reference.
As you say, a forwarding reference only works with type deduction in a function template. In your case, when you say T&&, T is int. It can't be int& because it has been explicitly stated in your Bar instantiation. As such, reference-collapsing rules can't occur, so you can't do perfect forwarding.
If you want to do perfect forwarding in a member function like that, you need to have a member function template:
template <typename U>
void foo(U&& arg)
{
std::forward<U>(arg); //actually do something here
}
If you absolutely need U to have the same unqualified type as T, you can do a static_assert:
template <typename U>
void foo(U&& arg)
{
static_assert(std::is_same<std::decay_t<U>,std::decay_t<T>>::value,
"U must be the same as T");
std::forward<U>(arg); //actually do something here
}
std::decay might be a bit too aggressive for you as it will decay array types to pointers. If that's not what you want, you could write your own simple trait:
template <typename T>
using remove_cv_ref = std::remove_cv_t<std::remove_reference_t<T>>;
template <typename T, typename U>
using is_equiv = std::is_same<remove_cv_ref<T>, remove_cv_ref<U>>;
If you need a variadic version, we can write an are_equiv trait. First we need a trait to check if all traits in a pack are true. I'll use the bool_pack method:
namespace detail
{
template<bool...> struct bool_pack;
template<bool... bs>
using all_true = std::is_same<bool_pack<bs..., true>, bool_pack<true, bs...>>;
}
template <typename... Ts>
using all_true = detail::all_true<Ts::value...>;
Then we need something to check if each pair of types in Ts... and Us... satisfy is_equiv. We can't take two parameter packs as template arguments, so I'll use std::tuple to separate them (you could use a sentinel node, or split the pack halfway through instead if you wanted):
template <typename TTuple, typename UTuple>
struct are_equiv;
template <typename... Ts, typename... Us>
struct are_equiv <std::tuple<Ts...>, std::tuple<Us...>> : all_true<is_equiv<Ts,Us>...>
{};
Then we can use this like:
static_assert(are_equiv<std::tuple<Ts...>,std::tuple<Us...>>::value,
"Us must be equivalent to Ts");
You're right : "universal references" only appear when the type of a deduced parameter is T&&. In your case, there is no deduction (T is known from the class), hence no universal reference.
In your snippet, std::forward will always perform std::move as arg is a regular rvalue reference.
If you wish to generate a universal reference, you need to make foo a function template :
template <typename T>
class Bar
{
public:
template <typename U>
void foo(U&& arg)
{
std::forward<U>(arg);
}
};