I need to create a tuple-like variadic class template that takes an arbitrary number of functions, each taking a std::array as an argument. Example:
template <unsigned SZ>
using func_t = void(*)(std::array<int, SZ>);
template <unsigned SZ, func_t<SZ>... Fs>
struct FuncWrapper
{};
template <unsigned SZ, func_t<SZ> F, func_t<SZ>... Fs>
struct FuncWrapper<SZ, F, Fs...>: public FuncWrapper<SZ, Fs...>
{};
This is giving me the error
class template partial specialization is not more specialized than the
primary template
which I'm guessing might have to do with the fact that I'm passing the array size twice; once as a template argument to the FuncWrapper class, and then again as a template argument to the array. This is only happening with C++17; it works in C++14 and earlier. How can I redesign this to get the template specialization to work right?
Related
I recently asked a question about determining whether an iterator points to a complex value at compile time and received an answer that works.
The question is here:
How can I specialize an algorithm for iterators that point to complex values?
And the solution was a set of templates that determine whether one template is a specialization of another:
template <class T, template <class...> class Template>
struct is_specialization : std::false_type {};
template <template <class...> class Template, class... Args>
struct is_specialization<Template<Args...>, Template> : std::true_type {};
This does work, but I am really struggling to understand how this works. Particularly the nested template within a template is confusing to me. I'm also still fairly new to using variadic templates and it seems odd to have a variadic template with no type provided, for example: <class...> instead of something like this <class... Args>.
Can someone please break down this template and describe how it gets resolved?
You have to take in count that there are three types of template parameters:
1) types
2) non-types (or values)
3) template-templates
The first type is preceded by typename (or class)
template <typename T>
void foo (T const & t);
In the preceding example, T is a type and t (a classical function argument) is a value of type T.
The second type of template parameter are values and are preceded by the type of the value (or auto, starting from C++17, for a not specified type)
template <int I>
void bar ()
{ std::cout << I << std::endl; }
In the preceding example the I template parameter is a value of type int.
The third type is the most complex to explain.
Do you know (I suppose) that std::vector<int> and std::vector<double> are different types, but they have in common std::vector, a template class.
A template-template parameter is a parameter that accept std::vector, the template class without arguments.
A template-template parameter is preceded by a template keyword, as in the following example
template <template <int> class C>
void baz ();
The template-template parameter C in the preceding example is class (or struct) that require a single int (value) template parameter.
So if you have a class
template <int I>
class getInt
{ };
you can pass getInt, as template parameter, to baz()
baz<getInt>();
Now you should be able to understand your code:
template <class T, template <class...> class Template>
struct is_specialization : std::false_type {};
the is_specialization struct is a template struct that receive, as template parameters, a type (T) and a template-template Template that accept classes/structs receiving a variadic number of type template parameters.
Now you have a specialization of is_specialization:
template <template <class...> class Template, class... Args>
struct is_specialization<Template<Args...>, Template> : std::true_type {};
This specialization is selected when the first template parameter (Template<Args...>) is a class based on the second (Template).
An example: if you instantiate
is_specialization<std::vector<int>, std::map>
the main version (that inherit from std::false_type) is selected because std::vector<int> isn't based on std::map.
But if you instantiate
is_specialization<std::vector<int>, std::vector>
the specialization (that inherit from std::true_type) is selected because std::vector<int> is based on std::vector.
This is a answer based on what caused "click" in my head when it comes to this, I am sure max66 answer is much more useful if you can follow it. If like me even after reading about this you are still strugling here is my attempt to explain this.
Do not read the first lines of templates
So this:
template <class T, template <class...> class Template>
struct is_specialization : std::false_type {};
template <template <class...> class Template, class... Args>
struct is_specialization<Template<Args...>, Template> : std::true_type {};
becomes this:
struct is_specialization : std::false_type {};
struct is_specialization<Template<Args...>, Template> : std::true_type {};
Now things are bit simpler. We have first general case, and second special case(that is prefered by compiler when he can match the arguments).
By match I do not mean anything fancy, almost literal text matching.
So for example if we do
is_specialization<std::vector<double>, std::set>
Now compiler will try to match special case.
You can imagine it literally replacing the Template with std::vector (if he is matching on the first argument first), and then fail since he now expects Template to mean std::vector and in the second argument it is std::set.
He also replaced Args... with double but that is not so important since that is not the cause of mismatch.
Similarly compiler could have first tried to match second argument and conclude that Template must be std::set, and then he fails since first argument is not std::set<Args...>.
I consider the first lines of templates much less important and easier to understand, but for completeness let's go over them.
template <class T, template <class...> class Template>
Just means that this is a template whose second arg is template template argument, this is needed so you can pass something like std::set as second argument(note that normally this will not work, you would need to pass std::set of some type, e.g. std::set<float>).
The ugliest part is the first line of second template:
template <template <class...> class Template, class... Args>
Here again if we remember what we do in specialization code makes sense.
We have template template argument Template, and Args are needed just because we want to use template template argument as normal template argument(as first arg(Template<Args...>)).
tl;dr
Ignore first lines of template, pattern match.
Disclaimer: Like I said this is just my way of explaining this code to myself, I know that standard people would cry reading phrases like "first line" or "pattern match".
Trying to understand this bit of code using variadic templates:
template <typename... T>
struct FooGroup;
template <typename... FooTypes, typename... BarTypes>
struct TEST<FooGroup<FooTypes...>, BarGroup<BarTypes...>>
I know variadic templates allows you to pass in unknown type of unknown amount. However, this is confusing to me. Can anyone shed some light?
This code
template <typename... T>
struct FooGroup;
declare FooGroup as a struct that receive a variadic list of type template parameter.
template <typename... FooTypes, typename... BarTypes>
struct TEST<FooGroup<FooTypes...>, BarGroup<BarTypes...>>
is part of template specialization.
I suppose that TEST is declared as follows
template <typename, typename>
struct TEST;
so receiving two template types parameter.
With
template <typename... FooTypes, typename... BarTypes>
struct TEST<FooGroup<FooTypes...>, BarGroup<BarTypes...>>
you declare a partial specialization of TEST in case the first template parameter is in the form FooGroup<FooTypes...> (where FooTypes... is a variadic list of template parameters) and the second template parameter is in the form BarGroup<BarTypes...> (where I suppose BarGroup is defined almost as FooGroup and BarTypes... is another variadic list of template parameters)
I want to check if a class is a template specialization of another one. What I have tried is:
template <class T, template <class...> class Template>
struct is_specialization : std::false_type {};
template <template <class...> class Template, class... Args>
struct is_specialization<Template<Args...>, Template> : std::true_type {};
It works fine when all template parameters are type arguments but not when some are non-type arguments. For example it works with std::vector but not std::array (since the later accepts an non-type argument std::size_t).
It's important that the check is made at compile time. Also the solution must work for any template, not just vectors or arrays. That means that it can be any number of type arguments and any number of non-type arguments. For example it should work with template <class A, bool B, class C, int D, class... Args> class foo;
C++20 is a weird, weird world. Cross-checking is welcome as I'm a beginner with CTAD and not entirely sure I've covered all bases.
This solution uses SFINAE to check whether class template argument deduction (CTAD) succeeds between the requested class template and the mystery type. An additional is_same check is performed to prevent against unwanted conversions.
template <auto f>
struct is_specialization_of {
private:
template <class T>
static auto value_impl(int) -> std::is_same<T, decltype(f.template operator()<T>())>;
template <class T>
static auto value_impl(...) -> std::false_type;
public:
template <class T>
static constexpr bool value = decltype(value_impl<T>(0))::value;
};
// To replace std::declval which yields T&&
template <class T>
T declrval();
#define is_specialization_of(...) \
is_specialization_of<[]<class T>() -> decltype(__VA_ARGS__(declrval<T>())) { }>::value
// Usage
static_assert(is_specialization_of(std::array)<std::array<int, 4>>);
First caveat: Since we can't declare a parameter for the class template in any way without knowing its arguments, passing it around to where CTAD will be performed can only be done by jumping through some hoops. C++20 constexpr and template-friendly lambdas help a lot here, but the syntax is a mouthful, hence the helper macro.
Second caveat: this only works with movable types, as CTAD only works on object declarations, not reference declarations. Maybe a future proposal will allow things such as std::array &arr = t;, and then this will be fixed!
Actually fixed by remembering that C++17 has guaranteed copy-elision, which allows direct-initialization from a non-movable rvalue as is the case here!
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;
};
This answer forward declare template class Memo one way, and implemented it's partial specialization in another way.
So, the forward declaration is :
template <template <typename...> class Container, typename...> struct Memo;
and the partial specialization this :
template <typename R, typename... Args, template <typename...> class Container>
struct Memo<Container, R, std::tuple<Args...>>
The Container template argument is switched with the variadic template argument (hopefully someone understood what I just wrote). The R is just the first element in the pack.
The code compiles fine, so I guess there should be a simple explanation of why it is allowed to do.
So, why is it allowed to switch template arguments in a template specialization? Is it because a template specialization is an independent type?
The order of the template argumments in a partial template specialization doesn't matter at all. Thats because a partial template specialization is not really a template, its only an specialization of an existing template.
That means the set of template parameters of a partial template specialization are not the parameters of a template, are only the declaration of the set of generic parameters which the specialization uses/needs.
Consider an example:
// A typelist template:
template<typename... Ts>
struct type_list {};
// A metafunction for concatenating typelists (Forward declaration):
template<typename LIST1 , typename LIST2>
struct concat;
//The partial specialization of that template for typelists:
template<typename... Ts , typename... Us>
struct concat<type_list<Ts...> , type_list<Us...>>
{
using result = type_list<Ts...,Us...>;
};
In the partial specialization of the concat metafunction, what we are doing is to specialize the template for two unspecified typelists. So the template parameters of the typelits are unspecified, that is, are generic. And we need to "declare" that generic parameters before using it.
Consider the template parameters of a partial template specialization only as declarations of the generic paraeters which the specialization needs. Because there are not real template parameters, they don't have the restrictions of template parameters: Note that in the specialization I have used two variadic packs, which is not allowed in a template.