patterncppModerate
Filtering variadic template arguments
Viewed 0 times
argumentstemplatevariadicfiltering
Problem
I wrote some code to implement a way to filter the arguments of a variadic template based on their type.
My current solution requires many helper meta-functions and I'm sure that there must be a better way. How would you improve this?
```
#include
#include
//Type for storing a variadic template
template
struct list {};
//helper meta-functions
//list_rename function. Renames any template type. Needed to convert list to tuple later
template class TargetT>
struct list_rename_impl;
template class SourceT, typename ... Args, template class TargetT>
struct list_rename_impl, TargetT>
{
using type = TargetT;
};
template class TargetT>
using list_rename = typename list_rename_impl::type;
//~END list_rename
//list_append function. Appends List U to T. Needed for the list_on_condition function
template
struct list_append_impl
{};
template
struct list_append_impl, list>
{
using type = list;
};
template
using list_append = typename list_append_impl::type;
//~END list_append
//list_on_condition function. Creates a list of all passed Elements for which Condition::value is true.
//This way a parameter pack can be filtered on a condition and stored
template class Condition, typename ... Elements>
struct list_on_condition_impl
{};
template class Condition>
struct list_on_condition_impl
{
using type = list<>; //No more elements to left, return empty list
};
template class Condition, typename Element>
struct list_on_condition_impl
{
using type = typename std::conditional //Only one element left. Return it, if it fullfills the Condition
::value,
list,
list<>
>::type;
};
template class Condition, typename Element, typename ... Elements>
struct list_on_condition_impl
{
using type = list_append //Check the first element and append it to the others recursively
::value,
list,
list<>
>::t
My current solution requires many helper meta-functions and I'm sure that there must be a better way. How would you improve this?
```
#include
#include
//Type for storing a variadic template
template
struct list {};
//helper meta-functions
//list_rename function. Renames any template type. Needed to convert list to tuple later
template class TargetT>
struct list_rename_impl;
template class SourceT, typename ... Args, template class TargetT>
struct list_rename_impl, TargetT>
{
using type = TargetT;
};
template class TargetT>
using list_rename = typename list_rename_impl::type;
//~END list_rename
//list_append function. Appends List U to T. Needed for the list_on_condition function
template
struct list_append_impl
{};
template
struct list_append_impl, list>
{
using type = list;
};
template
using list_append = typename list_append_impl::type;
//~END list_append
//list_on_condition function. Creates a list of all passed Elements for which Condition::value is true.
//This way a parameter pack can be filtered on a condition and stored
template class Condition, typename ... Elements>
struct list_on_condition_impl
{};
template class Condition>
struct list_on_condition_impl
{
using type = list<>; //No more elements to left, return empty list
};
template class Condition, typename Element>
struct list_on_condition_impl
{
using type = typename std::conditional //Only one element left. Return it, if it fullfills the Condition
::value,
list,
list<>
>::type;
};
template class Condition, typename Element, typename ... Elements>
struct list_on_condition_impl
{
using type = list_append //Check the first element and append it to the others recursively
::value,
list,
list<>
>::t
Solution
One reason you think you require a lot of helper metafunctions is that you haven't clearly separated the code for solving the problem (filtering a list) from the code for testing the solution (e.g.
Your actual solving the problem code, with redundant comments and newlines removed, boils down to this:
That's not particularly concise, but it's much less bad than you originally thought it was.
So let's work on removing the unnecessary parts. First of all, you have three specializations of
, because that case (an
Next, a style/safety note: For metaprogramming, you don't need to provide full definitions (i.e. class bodies) for your types; a declaration is usually all you need. Furthermore, providing just the declarations will prevent the user from accidentally trying to define variables of those types. So, for example, you should write
instead of
and so on throughout. This also means that if someone accidentally tries to refer to
Next: Since
I also suggest naming the metafunction
Similarly, I'd recommend
And for parameters whose names don't matter because they're unused — remove the unused names! (Unless you use a documentation-generation tool that needs the names for some reason, I guess.)
Consider using
With the above suggestions and a bit of trivial whitespace formatting (e.g.,
One minor improvement you could make would be to split out the subproblem of "concatenating an arbitrary number of
```
template struct concat;
template class Pred, typename T>
using filter_helper = std::conditional_t::value, list, list<>>;
template class Pred, typename... Ts>
using filter = typena
list_rename and A, B, C).Your actual solving the problem code, with redundant comments and newlines removed, boils down to this:
template struct list {};
template struct list_append_impl {};
template
struct list_append_impl, list> {
using type = list;
};
template
using list_append = typename list_append_impl::type;
template class Condition, typename ... Elements>
struct list_on_condition_impl {};
template class Condition>
struct list_on_condition_impl {
using type = list<>;
};
template class Condition, typename Element>
struct list_on_condition_impl {
using type = typename std::conditional::value,
list,
list<>
>::type;
};
template class Condition, typename Element, typename ... Elements>
struct list_on_condition_impl {
using type = list_append::value,
list,
list<>
>::type,
typename list_on_condition_impl::type
>;
};
template class Condition, typename ... Elements>
using list_on_condition = typename list_on_condition_impl::type;That's not particularly concise, but it's much less bad than you originally thought it was.
So let's work on removing the unnecessary parts. First of all, you have three specializations of
list_on_condition_impl; that's a red flag, because you should basically always have two specializations of any recursive template — you should have one base case and one recursive case. If you have two base cases, something is probably wrong. You can safely remove the middle case,template class Condition, typename Element>
struct list_on_condition_impl {
using type = typename std::conditional::value,
list,
list<>
>::type;
};, because that case (an
Elements list of length 1) will be handled perfectly fine by the recursive case.Next, a style/safety note: For metaprogramming, you don't need to provide full definitions (i.e. class bodies) for your types; a declaration is usually all you need. Furthermore, providing just the declarations will prevent the user from accidentally trying to define variables of those types. So, for example, you should write
template struct list_append_impl;instead of
template struct list_append_impl {};and so on throughout. This also means that if someone accidentally tries to refer to
list_append_impl::type, they'll get a hard compiler error implicit instantiation of undefined template 'list_append_impl' instead of a SFINAE-friendly error no type named 'type' in 'list_append_impl'.Next: Since
list_append_impl is just an implementation detail, you probably don't need to define a convenience alias list_append for it. You could just use typename list_append_impl::type in the couple of places you need it. This would kill off three more lines.I also suggest naming the metafunction
filter, instead of list_on_condition; the former conveys exactly what the metafunction is supposed to do, while the latter is pretty opaque to anybody reading the code.Similarly, I'd recommend
Predicate over Condition, Ts... and Us... over LHS... and RHS..., and T and Rest... over Element and Elements....And for parameters whose names don't matter because they're unused — remove the unused names! (Unless you use a documentation-generation tool that needs the names for some reason, I guess.)
Consider using
std::conditional_t in place of typename std::conditional::type, if you're using a recent enough C++14 library.With the above suggestions and a bit of trivial whitespace formatting (e.g.,
typename... in place of typename ...), we get this:template struct list;
template struct list_append_impl;
template
struct list_append_impl, list> {
using type = list;
};
template class, typename...>
struct filter_impl;
template class Predicate>
struct filter_impl {
using type = list<>;
};
template class Predicate, typename T, typename... Rest>
struct filter_impl {
using type = typename list_append_impl::value,
list,
list<>
>,
typename filter_impl::type
>::type;
};
template class Predicate, typename... Ts>
using filter = typename filter_impl::type;One minor improvement you could make would be to split out the subproblem of "concatenating an arbitrary number of
lists", so that you could write filter_impl without the recursion.```
template struct concat;
template class Pred, typename T>
using filter_helper = std::conditional_t::value, list, list<>>;
template class Pred, typename... Ts>
using filter = typena
Code Snippets
template <typename ...> struct list {};
template <typename T, typename U> struct list_append_impl {};
template <typename ... LHS, typename ... RHS>
struct list_append_impl<list<LHS...>, list<RHS...>> {
using type = list<LHS..., RHS...>;
};
template <typename T, typename U>
using list_append = typename list_append_impl<T, U>::type;
template <template <typename> class Condition, typename ... Elements>
struct list_on_condition_impl {};
template <template <typename> class Condition>
struct list_on_condition_impl<Condition> {
using type = list<>;
};
template <template <typename> class Condition, typename Element>
struct list_on_condition_impl<Condition, Element> {
using type = typename std::conditional<
Condition<Element>::value,
list<Element>,
list<>
>::type;
};
template <template <typename> class Condition, typename Element, typename ... Elements>
struct list_on_condition_impl<Condition, Element, Elements...> {
using type = list_append<
typename std::conditional<
Condition<Element>::value,
list<Element>,
list<>
>::type,
typename list_on_condition_impl<
Condition,
Elements...
>::type
>;
};
template <template <typename> class Condition, typename ... Elements>
using list_on_condition = typename list_on_condition_impl<Condition, Elements...>::type;template <template <typename> class Condition, typename Element>
struct list_on_condition_impl<Condition, Element> {
using type = typename std::conditional<
Condition<Element>::value,
list<Element>,
list<>
>::type;
};template <typename T, typename U> struct list_append_impl;template <typename T, typename U> struct list_append_impl {};template <typename...> struct list;
template <typename, typename> struct list_append_impl;
template <typename... Ts, typename... Us>
struct list_append_impl<list<Ts...>, list<Us...>> {
using type = list<Ts..., Us...>;
};
template <template <typename> class, typename...>
struct filter_impl;
template <template <typename> class Predicate>
struct filter_impl<Predicate> {
using type = list<>;
};
template <template <typename> class Predicate, typename T, typename... Rest>
struct filter_impl<Predicate, T, Rest...> {
using type = typename list_append_impl<
std::conditional_t<
Predicate<T>::value,
list<T>,
list<>
>,
typename filter_impl<Predicate, Rest...>::type
>::type;
};
template <template <typename> class Predicate, typename... Ts>
using filter = typename filter_impl<Predicate, Ts...>::type;Context
StackExchange Code Review Q#115740, answer score: 11
Revisions (0)
No revisions yet.