Explicitly use defaults for some parameters in class template instantiation - c++

A class template can have multiple parameters that all have defaults.
template<typename UnderlyingT0 = int, typename UnderlyingtT1 = long, typename StringT = std::string>
struct options;
Instatiating the template with just default parameters is easy:
options<> my_default_options;
But what if I want to change a subset of parameters?
options<int, int, std::wstring> wstring_options;
It is not obvious that int is a default for the first parameter while for the second it isn't. Is there something like
options<default, int, std::wstring> wstring_options;
in C++?

No, there is nothing in standard C++ which would enable this. One option, noted by #FlorisVelleman in the comments, is to introduce an alias template:
template <class UnderlyingT1, class StringT = std::string>
using options_defT0 = options<int, UnderlyingT1, StringT>;
This has the drawback of having to explicitly duplicate the default argument of UnderlyingT0 in the alias definition, but as least it' duplicated in one place only.
An alternative option is used by many Boost libraries. They introduce a special tag use_default and make that the default value. Something like this:
struct use_default {};
template<typename UnderlyingT0 = use_default, typename UnderlyingtT1 = use_default, typename StringT = use_default>
struct options
{
using RealUnderlyingT0 = typename std::conditional<
std::is_same<UnderlyingT0, use_default>::value,
int,
UnderlyingT0
>::type;
using RealUnderlyingT1 = typename std::conditional<
std::is_same<UnderlyingT1, use_default>::value,
long,
UnderlyingT1
>::type;
using RealStringT = typename std::conditional<
std::is_same<StringT, use_default>::value,
std::string,
StringT
>::type;
};
Here, the downsides are that 1. you cannot tell the default arguments by looking at the template declaration, and 2. options<> and options<int, long, std::string> are different types.
The former can be fixed by good documentation, and the latter can probably be helped by judicious use of conversion functions and base classes.

There isn't a way to reuse the default parameters directly. You can use Floris's comment as a way to provide shorthands for common uses, but the template alias will still repeat the defaults.
Alternatively, the options struct could be set up to allow switching out parameters:
template <typename UnderlyingT0 = int,
typename UnderlyingT1 = long,
typename StringT = std::string>
struct options {
template <typename NewT0>
using WithT0 = options<NewT0, UnderylingT1, StringT>;
template <typename NewT1>
using WithT1 = options<UnderylingT0, NewT1, StringT>;
template <typename NewStringT>
using WithStringT = options<UnderylingT0, UnderylingT1, NewStringT>;
};
And then use it as
options<>::WithT1<int>::WithStringT<std::wstring>

If all your template arguments have defaults like in your example, you could create a helper struct to extract them for you.
template <class T, size_t N>
struct default_for_helper;
template <template <typename...> class T, size_t N, typename... Args>
struct default_for_helper<T<Args...>, N>
{
using type = std::tuple_element_t<N, std::tuple<Args...>>;
};
template <template <typename...> class T, size_t N>
using default_for = typename default_for_helper<T<>, N>::type;
Then use it like this:
options<default_for<options,0>, int, std::string> o;

I came across this issue again and came up with a more general version of Sebastian Redl's solution.
//given an index to replace at, a type to replace with and a tuple to replace in
//return a tuple of the same type as given, with the type at ReplaceAt set to ReplaceWith
template <size_t ReplaceAt, typename ReplaceWith, size_t... Idxs, typename... Args>
auto replace_type (std::index_sequence<Idxs...>, std::tuple<Args...>)
-> std::tuple<std::conditional_t<ReplaceAt==Idxs, ReplaceWith, Args>...>;
//instantiates a template with the types held in a tuple
template <template <typename...> class T, typename Tuple>
struct type_from_tuple;
template <template <typename...> class T, typename... Ts>
struct type_from_tuple<T, std::tuple<Ts...>>
{
using type = T<Ts...>;
};
//replaces the type used in a template instantiation of In at index ReplateAt with the type ReplaceWith
template <size_t ReplaceAt, typename ReplaceWith, class In>
struct with_n;
template <size_t At, typename With, template <typename...> class In, typename... InArgs>
struct with_n<At, With, In<InArgs...>>
{
using tuple_type = decltype(replace_type<At,With>
(std::index_sequence_for<InArgs...>{}, std::tuple<InArgs...>{}));
using type = typename type_from_tuple<In,tuple_type>::type;
};
//convenience alias
template <size_t ReplaceAt, typename ReplaceWith, class In>
using with_n_t = typename with_n<ReplaceAt, ReplaceWith, In>::type;
Advantages:
Flexible selection of parameters to change
Doesn't require changing of the original class
Supports classes which have some parameters without defaults
options<int,long,int> and with_n_t<2,int,options<>> are the same type
Some usage examples:
with_n_t<1, int, options<>> a; //options<int, int, std::string>
with_n_t<2, int,
with_n_t<1, int, options<>>> b; //options<int, int, int>
You could further generalise this to take variadic pairs of indices and types so that you don't need to nest with_n_t.

Related

How to get an element of type list by index

How can an element of a type list using L = type_list<T1, T2, ...> be retrieved by index, like std::tuple_element, preferrably in a non recursive way?
I want to avoid using tuples as type lists for use cases, that require instantiation for passing a list like f(L{}).
template<typename...> struct type_list {};
using L = typelist<int, char, float, double>;
using T = typeAt<2, L>; // possible use case
Not sure if an iteration using std::index_sequence and a test via std::is_same of the std::integral_constant version of the index is a good aproach.
I want to avoid using tuples as type lists for use cases, that require
instantiation for passing a list like f(L{})
If you don't want to instanciate std::tuple but you're ok with it in
unevaluated contexts, you may take advantage of std::tuple_element to
implement your typeAt trait:
template <std::size_t I, typename T>
struct typeAt;
template <std::size_t I, typename... Args>
struct typeAt<I, type_list<Args...>> : std::tuple_element<I, std::tuple<Args...>> {};
// ^ let library authors do the work for you
using L = type_list<int, char, float, double>;
using T = typename typeAt<2, L>::type;
static_assert(std::is_same<T, float>::value, "");
Using Boost.Mp11, this is a one-liner (as always):
template<typename...> struct type_list {};
using L = typelist<int, char, float, double>;
using T = mp_at_c<L, 2>; // <==
Note that this will compile significantly more efficiently than using tuple_element. On clang, the implementation even uses a compiler intrinsic.
You might re-implement a simplified non-recursive tuple-like version if you don't want to use std::tuple:
template <std::size_t I, typename T>
struct type_list_leaf
{
using type = T;
};
template <typename T> struct tag{ using type = T; };
template <typename Seq, typename...>
struct type_list_impl;
template <std::size_t... Is, typename... Ts>
struct type_list_impl<std::index_sequence<Is...>, Ts...> : type_list_leaf<Is, Ts>...
{
};
template <std::size_t I, typename T>
tag<T> type_list_element_tag(const type_list_leaf<I, T>&);
template <std::size_t I, typename Tuple>
using tuple_element = decltype(type_list_element_tag<I>(std::declval<Tuple>()));
template <std::size_t I, typename Tuple>
using tuple_element_t = typename tuple_element<I, Tuple>::type;
template <typename ... Ts>
using type_list = type_list_impl<std::make_index_sequence<sizeof...(Ts)>, Ts...>;
Demo

Filter a tuple list of types given a template template predicate

I was going to ask a question, but have found an answer on my own while writing it.
The question is how to filter a tuple given a template template predicate without specializing it.
Example usage:
using tuple_list_t = std::tuple<std::string, int, none, double, char, abc, bool>;
using tuple_found_expected_t = std::tuple<int, double, char, bool>;
// filtering a tuple
using tuple_filtered_t = filter_t<tuple_list_t, std::is_fundamental>;
static_assert(std::is_same<tuple_filtered_t, tuple_found_expected_t>(), "");
If someone have any advices or remarks about the problem, please, write an answer or a comment.
Sorry but your solution seems to me over-complicated. Particularly regarding the SFINAE part (why the void?).
What about simply as follows?
template <typename, template <typename...> class>
struct filter;
template <typename ... Ts,
template <typename...> class Pred>
struct filter<std::tuple<Ts...>, Pred>
{
using type = decltype(std::tuple_cat(std::declval<
std::conditional_t<Pred<Ts>::value,
std::tuple<Ts>,
std::tuple<>>>()...));
};
template <typename Tpl,
template <typename...> class Pred>
using filter_t = typename filter<Tpl, Pred>::type;
Here is a version of a template SFINAE struct to filter an std::tuple of types given a SFINAE predicate. So the invocation looks just like using an STL algorithm with an unary predicate.
Example:
using tuple_list_t = std::tuple<std::string, int, none, double, char, abc, bool>;
using tuple_found_expected_t = std::tuple<int, double, char, bool>;
// filtering a tuple
using tuple_filtered_t = filter_t<tuple_list_t, std::is_fundamental>;
static_assert(std::is_same<tuple_filtered_t, tuple_found_expected_t>(), "");
There were two major problems that I have faced.
How to pass a template as a template template parameter without specializing it:
eld::filter<tuple, std::is_fundamental>. Here std::is_fundamental is a template itself and expects to be specialized. But I can't specialize it before the invocation - I need to specialize it later with the types from a tuple.
Solution is simple. Just have a stub void (for example) default specializtion:
template <typename Tuple, template <typename> class SFINAEPredicate, typename = void>
Removing types that yield false with SFINAEPredicate<T>::value.
Now there is a way to use recursive SFINAE template specialization, but it requires a lot of typing and possibly a lot of redundant code generation. The latter is a big concern when compiling in debug, since MinGW will fail with assembler error file too big and up to this point I have found no working way to circumvent this problem (optimization pragmas don't do nothing). But that is out of scope of this question/answer.
The way I did it was with using type = decltype(std::tuple_cat(append_if_t<std::tuple<>, Types, SFINAEPredicate>()...));
append_if_t returns a tuple, which will be empty in some cases, and then all of them are concatenated in a final tuple with an std::tuple_cat.
So, there are N specializations for append_if_t which can be reused by the compiler and no recursive intermediate specializations.
Here is the code:
template<typename Tuple, typename T, template<typename> class /*SFINAEPredicate*/, typename = T>
struct append_if;
template<typename T, template<typename> class SFINAEPredicate, typename ... Types>
struct append_if<std::tuple<Types...>, T, SFINAEPredicate, T>
{
using type = typename std::conditional<SFINAEPredicate<T>::value,
std::tuple<Types..., T>, std::tuple<Types...>>::type;
};
template<typename Tuple, typename T, template<typename> class SFINAEPredicate, typename = T>
using append_if_t = typename append_if<Tuple, T, SFINAEPredicate>::type;
// TODO: function to traverse a tuple and find a type (index) that can be used to initialize a POD element
// pass a template parameter (trait validator) that accepts a type
// template for find_if in tuple
template<typename Tuple, template<typename> class /*SFINAEPredicate*/, typename = void>
struct filter;
template<template<typename> class SFINAEPredicate, typename ... Types>
struct filter<std::tuple<Types...>, SFINAEPredicate, void>
{
using type = decltype(std::tuple_cat(append_if_t<std::tuple<>, Types, SFINAEPredicate>()...));
};
template<typename Tuple, template<typename> class SFINAEPredicate, typename = void>
using filter_t = typename filter<Tuple, SFINAEPredicate>::type;

Concatenating tuples as types

I'm trying to practice some template programming. Maybe there's a standard way to do this, and I would be thankful for such answers, but my main goal is to practice the template programming techniques, so I tried to implement it myself:
I need to concatenate multiple tuples, but as types, not like std::cat_tuple does it. So I need something like cat<std::tuple<int, float>, std::tuple<char, bool>, ...> to get std::tuple<int, float, char, bool, ...> as a type.
My current attempt failed with a is not a template error:
/* Concat tuples as types: */
template <typename first_t, typename... rest_t> struct cat {
using type = typename _cat<first_t, typename cat<rest_t...>::type>::type;
^^^^ cat is not a template
};
template <typename first_t, typename second_t>
struct cat<first_t, second_t> {
using type = typename _cat<first_t, second_t>::type;
^^^^ cat is not a template
};
// Concat two tuples:
template <typename, typename> struct _cat;
template <typename tuple_t, typename first_t, typename... rest_t>
struct _cat<tuple_t, std::tuple<first_t, rest_t...>> {
using type = typename _cat<typename append<first_t, tuple_t>::type, std::tuple<rest_t...>>::type;
};
template <typename tuple_t, typename first_t>
struct _cat<tuple_t, std::tuple<first_t>> {
using type = typename append<first_t, tuple_t>::type;
};
// Prepend element to tuple:
template <typename, typename> struct prepend;
template <typename elem_t, typename... tuple_elem_t>
struct prepend<elem_t, std::tuple<tuple_elem_t...>> {
using type = std::tuple<elem_t, tuple_elem_t...>;
};
// Apppend element to tuple:
template <typename, typename> struct append;
template <typename elem_t, typename... tuple_elem_t>
struct append<elem_t, std::tuple<tuple_elem_t...>> {
using type = std::tuple<tuple_elem_t..., elem_t>;
};
What may be causing the error?
Is this a good approach? It might be solved in a simpler way, but I wanted it to be multi-purpose (with the append/prepend operations etc.).
How about a one-liner direct template aliase:
template<typename ... input_t>
using tuple_cat_t=
decltype(std::tuple_cat(
std::declval<input_t>()...
));
tuple_cat_t<
std::tuple<int,float>,
std::tuple<int>
> test{1,1.0f,2};
After reordering the definition a little, your code works fine.
I don't think that there are any guidelines for template meta programming. Probably due to the fact that the C++ committee is enhancing TMP "aggressively" and too few people is using TMP.
Here is my version of Cat, it basically follows the same structure as yours:
template <class, class>
struct Cat;
template <class... First, class... Second>
struct Cat<std::tuple<First...>, std::tuple<Second...>> {
using type = std::tuple<First..., Second...>;
};
Is too late to play?
I propose the following solution
template <typename T, typename ...>
struct cat
{ using type = T; };
template <template <typename ...> class C,
typename ... Ts1, typename ... Ts2, typename ... Ts3>
struct cat<C<Ts1...>, C<Ts2...>, Ts3...>
: public cat<C<Ts1..., Ts2...>, Ts3...>
{ };
Observe that this solution doesn't works only with a variadic list of std::tuple but also with a generic container of types. If a std::tuple-only solution is enough for you, you can simplify it as follows
template <typename T, typename ...>
struct cat
{ using type = T; };
template <typename ... Ts1, typename ... Ts2, typename ... Ts3>
struct cat<std::tuple<Ts1...>, std::tuple<Ts2...>, Ts3...>
: public cat<std::tuple<Ts1..., Ts2...>, Ts3...>
{ };
You can test that works with
using t1 = typename cat<std::tuple<int, float>,
std::tuple<char, bool>,
std::tuple<long, char, double>>::type;
using t2 = std::tuple<int, float, char, bool, long, char, double>;
static_assert(std::is_same<t1, t2>::value, "!");
-- EDIT --
As pointed by felix (thanks!) with my precedent solution we have that
std::is_same<int, typename cat<int>::type>::value == true
that is... cat<T>::type is defined also when T isn't a std::tuple.
This is a problem?
I don't know because I don't know how is used cat<T>::type.
Anyway... avoid it imposing that cat<Ts...>::type is defined only when all Ts... are type containers (with the same container), it's simple: the main version for cat become only declared but not defined
template <typename, typename ...> // or also template <typename...>
struct cat;
and is introduced an additional specialization with a single type (but only when it's a type container).
template <template <typename ...> class C, typename ... Ts1>
struct cat<C<Ts1...>>
{ using type = C<Ts1...>; };

Bind metafunction: accept both types and template template parameters (accept anything)

I'm trying to write a Bind metaprogramming template helper metafunction that binds a template parameter to something.
I have a working implementation for simple template metafunctions:
template<typename T0, typename T1>
struct MakePair
{
using type = std::pair<T0, T1>;
};
template<template<typename...> class TF, typename... Ts>
struct Bind
{
template<typename... TArgs>
using type = TF<Ts..., TArgs...>;
};
using PairWithInt = typename Bind<MakePair, int>::type;
static_assert(std::is_same<PairWithInt<float>, MakePair<int, float>>{}, "");
But what if MakePair's template arguments were template templates? Or simple numerical values?
template<template<typename> class T0, template<typename> class T1>
struct MakePair0
{
using type = /*...*/;
};
template<template<typename...> class TF, template<typename> class... Ts>
struct Bind0 { /*...*/ }
// ...
template<int T0, int T1>
struct MakePair1
{
using type = /*...*/;
};
template<template<int...> class TF, int... Ts>
struct Bind1 { /*...*/ }
A lot of unnecessary repetition. It gets unmanageable if template arguments are mixed between types, template templates, and integral constants.
Is something like the following piece of code possible?
template<template<ANYTHING...> class TF, ANYTHING... Ts>
struct BindAnything
{
template<ANYTHING... TArgs>
using type = TF<Ts..., TArgs...>;
};
ANYTHING would accept types, template templates, template template templates, integral values, etc...
When I'm doing serious metaprogramming, I turn everything into types.
template<class T>struct tag{using type=T;};
template<class Tag>using type_t=typename Tag::type;
template<template<class...>class> struct Z {};
template<class Z, class...Ts>
struct apply {};
template<template<class...>class z, class...ts>
struct apply< Z<z>, ts... >:
tag< z<ts...> >
{};
template<class Z, class...Ts>
using apply_t = type_t< apply<Z, Ts...> >;
now we pass template<?> foo around as Z<foo>, and it is now a type.
Similar things can be done for constants, using std::integral_constant<T, t> (and easier to use aliases of same), or template<class T, T* p> struct pointer_constant {};, by turning them into types.
Once everything is a type, your metaprogramming becomes more uniform. Templates just become a kind of type on which apply_t does things to.
There is no way in C++ to have a template argument that can be a type, a value or a template. So this is about the best you can get.
templates not written for the above pattern need to be wrapped up, and their arguments "lifted" to being types. As an example:
template<class T, class t>
using number_constant = std::integral_constant< T, t{} >;
using number_constant_z = Z<number_constant>;
has had its arguments "lifted" from values to types, and then it has been wrapped with a Z to turn itself into a type.
Bind now reads:
template<class z, class... Ts>
struct Bind {
template<class... More>
using type_base = apply_t< z, Ts..., More... >;
using type = Z<type_base>;
};
template<class Z, class...Ts>
using Bind_t = type_t<Bind<Z,Ts...>>; // strip ::type
using Bind_z = Z<Bind_t>; // quote into a Z<?>
and Bind_z is a type wrapping a template that returns a wrapped template, and takes a type that wraps a template as its first argument.
To use it:
template<class...>struct types{using type=types;};
using types_z=Z<types>;
template<class...Ts>
using prefix =apply_t< Bind_z, types_z, Ts... >;
using prefix_z = Z<prefix>;
prefix_z takes a set of types, and generates a factory of types<?...> that will contain the prefix Ts... first.
apply_t< apply_t< prefix_z, int, double, char >, std::string >
is
types< int, double, char, std::string >
live example.
There is another fun approach: do metaprogramming in functions:
template<template<class...>class z, class...Ts>
constexpr auto apply_f( Z<z>, tag<Ts>... )
-> tag<z<Ts...>> { return {}; }
here, types are represented by values of type tag<t>, templates a Z<z> and values as std::integral_constant<?>.
These two:
template<class T>
constexpr tag<T> Tag = {};
template<template<class...>class z>
constexpr Z<z> Zag = {};
give you ways to get values that represent types and templates respectively.
#define TYPEOF(...) type_t<decltype(__VA_ARGS__)>
is a macro that moves from an instance of a tag to type type in the tag, and Tag<?> moves from a type to an instance of a tag.
TYPEOF( apply_f( apply_f( Zag<prefix>, Tag<int>, Tag<double>, Tag<char> ), Tag<std::string> ) )
is
apply_t< apply_t< prefix_z, int, double, char >, std::string >
strange, but can be interesting.
I think you're looking for quote and map here. First, you want something that given a "metafunction class" and a sequence of arguments gives you a new type:
template <typename MCls, typename... Args>
using map = typename MCls::template apply<Args...>;
As the implementation here suggests, a metafunction class is one that has an member alias template named apply.
To turn a class template into a metafunction class, we introduce quote:
template <template <typename...> class C>
struct quote {
template <typename... Args>
using apply = C<Args...>;
};
The above is sufficient to do something like:
using T = map<quote<std::tuple>, int, char, double>;
to yield the type:
std::tuple<int, char, double>
In your example, we could write:
using P = map<quote<MakePair>, int, char>::type; // std::pair<int, char>
but I would instead prefer to make MakePair a metafunction class directly:
struct MakePair2 {
template <typename T, typename U>
using apply = std::pair<T, U>;
};
using P = map<MakePair2, int, char>; // also std::pair<int, char>
that avoids the extra ::type.
Consistently use the concepts of metafunction (a type with a member typedef named type, e.g. map) and a metafunction class (a type with a member template alias named apply, e.g. quote) and use only those concepts throughout your metaprogramming code. Values and class templates are second-class citizens.

Currying for templates in C++ metaprogramming

This is more of a conceptual question. I'm trying to find the easiest way of converting a two-arg template (the arguments being types) into a one-arg template. I.e., binding one of the types.
This would be the meta-programming equivalent of bind in boost/std. My example includes a possible use-case, which is, passing std::is_same as template argument to a template that takes a one-arg template template argument (std::is_same being a two-arg template), i.e. to TypeList::FindIf. The TypeList is not fully implemented here, neither is FindIf, but you get the idea. It takes a "unary predicate" and returns the type for which that predicate is true, or void if not such type.
I have 2 working variants but the first is not a one-liner and the 2nd uses a rather verbose BindFirst contraption, that would not work for non-type template arguments. Is there a simple way to write such a one-liner? I believe the procedure I'm looking for is called currying.
#include <iostream>
template<template<typename, typename> class Function, typename FirstArg>
struct BindFirst
{
template<typename SecondArg>
using Result = Function<FirstArg, SecondArg>;
};
//template<typename Type> using IsInt = BindFirst<_EqualTypes, int>::Result<Type>;
template<typename Type> using IsInt = std::is_same<int, Type>;
struct TypeList
{
template<template<typename> class Predicate>
struct FindIf
{
// this needs to be implemented, return void for now
typedef void Result;
};
};
int main()
{
static_assert(IsInt<int>::value, "");
static_assert(!IsInt<float>::value, "");
// variant #1: using the predefined parameterized type alias as predicate
typedef TypeList::FindIf<IsInt>::Result Result1;
// variant #2: one-liner, using BindFirst and std::is_same directly
typedef TypeList::FindIf< BindFirst<std::is_same, int>::Result>::Result Result2;
// variant #3: one-liner, using currying?
//typedef TypeList::FindIf<std::is_same<int, _>>::Result Result2;
return 0;
}
Click here for code in online compiler GodBolt.
I think the typical way of doing this is keep everything in the world of types. Don't take template templates - they're messy. Let's write a metafunction named ApplyAnInt that will take a "metafunction class" and apply int to it:
template <typename Func>
struct ApplyAnInt {
using type = typename Func::template apply<int>;
};
Where a simple metafunction class might be just checking if the given type is an int:
struct IsInt {
template <typename T>
using apply = std::is_same<T, int>;
};
static_assert(ApplyAnInt<IsInt>::type::value, "");
Now the goal is to support:
static_assert(ApplyAnInt<std::is_same<_, int>>::type::value, "");
We can do that. We're going to call types that contain _ "lambda expressions", and write a metafunction called lambda which will either forward a metafunction class that isn't a lambda expression, or produce a new metafunction if it is:
template <typename T, typename = void>
struct lambda {
using type = T;
};
template <typename T>
struct lambda<T, std::enable_if_t<is_lambda_expr<T>::value>>
{
struct type {
template <typename U>
using apply = typename apply_lambda<T, U>::type;
};
};
template <typename T>
using lambda_t = typename lambda<T>::type;
So we update our original metafunction:
template <typename Func>
struct ApplyAnInt
{
using type = typename lambda_t<Func>::template apply<int>;
};
Now that leaves two things: we need is_lambda_expr and apply_lambda. Those actually aren't so bad at all. For the former, we'll see if it's an instantiation of a class template in which one of the types is _:
template <typename T>
struct is_lambda_expr : std::false_type { };
template <template <typename...> class C, typename... Ts>
struct is_lambda_expr<C<Ts...>> : contains_type<_, Ts...> { };
And for apply_lambda, we just will substitute the _ with the given type:
template <typename T, typename U>
struct apply_lambda;
template <template <typename...> class C, typename... Ts, typename U>
struct apply_lambda<C<Ts...>, U> {
using type = typename C<std::conditional_t<std::is_same<Ts, _>::value, U, Ts>...>::type;
};
And that's all you need actually. I'll leave extending this out to support arg_<N> as an exercise to the reader.
Yeah, I had this issue to. It took a few iterations to figure out a decent way to do this. Basically, to do this, we need to specify a reasonable representation of what we want and need. I borrowed some aspects from std::bind() in that I want to specify the template that I wish to bind and the parameters that I want to bind to it. Then, within that type, there should be a template that will allow you to pass a set of types.
So our interface will look like this:
template <template <typename...> class OP, typename...Ts>
struct tbind;
Now our implementation will have those parameters plus a container of types that will be applied at the end:
template <template <typename...> class OP, typename PARAMS, typename...Ts>
struct tbind_impl;
Our base case will give us a template type, which I'll call ttype, that'll return a template of the contained types:
template <template <typename...> class OP, typename...Ss>
struct tbind_impl<OP, std::tuple<Ss...>>
{
template<typename...Us>
using ttype = OP<Ss...>;
};
Then we have the case of moving the next type into the container and having ttype refer to the ttype in the slightly simpler base case:
template <template <typename...> class OP, typename T, typename...Ts, typename...Ss>
struct tbind_impl<OP, std::tuple<Ss...>, T, Ts...>
{
template<typename...Us>
using ttype = typename tbind_impl<
OP
, std::tuple<Ss..., T>
, Ts...
>::template ttype<Us...>;
};
And finally, we need a remap of the templates that will be passed to ttype:
template <template <typename...> class OP, size_t I, typename...Ts, typename...Ss>
struct tbind_impl<OP, std::tuple<Ss...>, std::integral_constant<size_t, I>, Ts...>
{
template<typename...Us>
using ttype = typename tbind_impl<
OP
, typename std::tuple<
Ss...
, typename std::tuple_element<
I
, typename std::tuple<Us...>
>::type
>
, Ts...
>::template ttype<Us...>;
Now, since programmers are lazy, and don't want to type std::integral_constant<size_t, N> for each parameter to remap, we specify some aliases:
using t0 = std::integral_constant<size_t, 0>;
using t1 = std::integral_constant<size_t, 1>;
using t2 = std::integral_constant<size_t, 2>;
...
Oh, almost forgot the implementation of our interface:
template <template <typename...> class OP, typename...Ts>
struct tbind : detail::tbind_impl<OP, std::tuple<>, Ts...>
{};
Note that tbind_impl was placed in a detail namespace.
And voila, tbind!
Unfortunately, there is a defect prior to c++17. If you pass tbind<parms>::ttype to a template that expects a template with a particular number of parameters, you will get an error as the number of parameters don't match (specific number doesn't match any number). This complicates things slightly requiring an additional level of indirection. :(
template <template <typename...> class OP, size_t N>
struct any_to_specific;
template <template <typename...> class OP>
struct any_to_specific<OP, 1>
{
template <typename T0>
using ttype = OP<T0>;
};
template <template <typename...> class OP>
struct any_to_specific<OP, 2>
{
template <typename T0, typename T1>
using ttype = OP<T0, T1>;
};
...
Using that to wrap tbind will force the compiler to recognize the template having the specified number of parameters.
Example usage:
static_assert(!tbind<std::is_same, float, t0>::ttype<int>::value, "failed");
static_assert( tbind<std::is_same, int , t0>::ttype<int>::value, "failed");
static_assert(!any_to_specific<
tbind<std::is_same, float, t0>::ttype
, 1
>::ttype<int>::value, "failed");
static_assert( any_to_specific<
tbind<std::is_same, int , t0>::ttype
, 1
>::ttype<int>::value, "failed");
All of which succeed.