Puisque je n'étais pas heureux avec ce que j'ai trouvé, j'ai essayé de trouver une solution moi-même et a fini par écrire une petite bibliothèque qui permet la formulation générique des opérations sur l'argument packs. Ma solution a les caractéristiques suivantes:
- Permet de parcourir l'ensemble ou certains des éléments d'un argument pack, éventuellement définis par le calcul de leurs indices sur l'emballage;
- Permet le transfert calculée parties d'un argument pack de variadic foncteurs;
- Exige seulement dont un relativement court fichier d'en-tête;
- Fait un usage intensif de transfert parfait pour permettre lourd inline et évite copies/se déplace pour permettre le minimum de perte de performances;
- La mise en œuvre interne de l'itération algorithmes repose sur le Vide de la Classe de Base d'Optimisation pour réduire la consommation de mémoire.
- Il est facile (relativement, étant donné que c'est le modèle de la méta-programmation) d'étendre et de s'adapter.
Je vais d'abord montrer ce qui peut être fait avec la bibliothèque, puis afficher sa mise en œuvre.
LES CAS D'UTILISATION
Voici un exemple de la façon dont l' for_each_in_arg_pack()
fonction peut être utilisée pour parcourir tous les arguments d'un pack et passer chaque argument en entrée de certains fournie par le client foncteur (bien sûr, le foncteur doit avoir un générique d'appel de l'opérateur si l'argument pack contient les valeurs de la hétérogène types):
// Simple functor with a generic call operator that prints its input. This is used by the
// following functors and by some demonstrative test cases in the main() routine.
struct print
{
template<typename T>
void operator () (T&& t)
{
cout << t << endl;
}
};
// This shows how a for_each_*** helper can be used inside a variadic template function
template<typename... Ts>
void print_all(Ts&&... args)
{
for_each_in_arg_pack(print(), forward<Ts>(args)...);
}
L' print
foncteur ci-dessus peut également être utilisé dans plus de calculs complexes. En particulier, voici comment itérer sur un sous-ensemble (dans ce cas, une sous-gamme) des arguments dans un pack:
// Shows how to select portions of an argument pack and
// invoke a functor for each of the selected elements
template<typename... Ts>
void split_and_print(Ts&&... args)
{
constexpr size_t packSize = sizeof...(args);
constexpr size_t halfSize = packSize / 2;
cout << "Printing first half:" << endl;
for_each_in_arg_pack_subset(
print(), // The functor to invoke for each element
index_range<0, halfSize>(), // The indices to select
forward<Ts>(args)... // The argument pack
);
cout << "Printing second half:" << endl;
for_each_in_arg_pack_subset(
print(), // The functor to invoke for each element
index_range<halfSize, packSize>(), // The indices to select
forward<Ts>(args)... // The argument pack
);
}
Parfois, on peut juste vouloir transférer une partie d'un argument pack de certains autres variadic foncteur au lieu d'une itération à travers ses éléments et passer chacun d'eux individuellement à un non-variadic foncteur. C'est ce que l' forward_subpack()
algorithme permet de faire:
// Functor with variadic call operator that shows the usage of for_each_***
// to print all the arguments of a heterogeneous pack
struct my_func
{
template<typename... Ts>
void operator ()(Ts&&... args)
{
print_all(forward<Ts>(args)...);
}
};
// Shows how to forward only a portion of an argument pack
// to another variadic functor
template<typename... Ts>
void split_and_print(Ts&&... args)
{
constexpr size_t packSize = sizeof...(args);
constexpr size_t halfSize = packSize / 2;
cout << "Printing first half:" << endl;
forward_subpack(my_func(), index_range<0, halfSize>(), forward<Ts>(args)...);
cout << "Printing second half:" << endl;
forward_subpack(my_func(), index_range<halfSize, packSize>(), forward<Ts>(args)...);
}
Pour des tâches plus spécifiques, il est bien sûr possible de récupérer les arguments spécifiques dans un pack à l' indexation . C'est ce que l' nth_value_of()
fonction vous permet d'effectuer, en collaboration avec ses assistants first_value_of()
et last_value_of()
:
// Shows that arguments in a pack can be indexed
template<unsigned I, typename... Ts>
void print_first_last_and_indexed(Ts&&... args)
{
cout << "First argument: " << first_value_of(forward<Ts>(args)...) << endl;
cout << "Last argument: " << last_value_of(forward<Ts>(args)...) << endl;
cout << "Argument #" << I << ": " << nth_value_of<I>(forward<Ts>(args)...) << endl;
}
Si l'argument pack est homogène sur l'autre main (c'est à dire tous les arguments du même type), une formulation telle que celui-ci serait peut-être préférable. L' is_homogeneous_pack<>
méta-fonction permet de déterminer si tous les types de paramètre pack sont homogènes, et est principalement destinée à être utilisée en static_assert()
déclarations:
// Shows the use of range-based for loops to iterate over a
// homogeneous argument pack
template<typename... Ts>
void print_all(Ts&&... args)
{
static_assert(
is_homogeneous_pack<Ts...>::value,
"Template parameter pack not homogeneous!"
);
for (auto&& x : { args... })
{
// Do something with x...
}
cout << endl;
}
Enfin, depuis les lambdas sont tout sucre syntaxique pour les foncteurs, ils peuvent être utilisés en combinaison avec les algorithmes ci-dessus; toutefois, jusqu'à ce générique lambdas seront pris en charge par le C++, ce n'est possible que pour homogène argument packs. L'exemple suivant montre également l'utilisation de l' homogeneous-type<>
méta-fonction, qui renvoie le type de tous les arguments dans un milieu homogène pack:
// ...
static_assert(
is_homogeneous_pack<Ts...>::value,
"Template parameter pack not homogeneous!"
);
using type = homogeneous_type<Ts...>::type;
for_each_in_arg_pack([] (type const& x) { cout << x << endl; }, forward<Ts>(args)...);
C'est fondamentalement ce que la bibliothèque permet d'en faire, mais je crois qu'il pourrait même être étendu à l'accomplissement de tâches plus complexes.
La mise en ŒUVRE
Maintenant vient la mise en place, ce qui est un peu plus difficile par elle-même, donc je vais utiliser des commentaires pour expliquer le code et éviter de faire ce post trop long (peut-être c'est déjà fait):
#include <type_traits>
#include <utility>
//===============================================================================
// META-FUNCTIONS FOR EXTRACTING THE n-th TYPE OF A PARAMETER PACK
// Declare primary template
template<int I, typename... Ts>
struct nth_type_of
{
};
// Base step
template<typename T, typename... Ts>
struct nth_type_of<0, T, Ts...>
{
using type = T;
};
// Induction step
template<int I, typename T, typename... Ts>
struct nth_type_of<I, T, Ts...>
{
using type = typename nth_type_of<I - 1, Ts...>::type;
};
// Helper meta-function for retrieving the first type in a parameter pack
template<typename... Ts>
struct first_type_of
{
using type = typename nth_type_of<0, Ts...>::type;
};
// Helper meta-function for retrieving the last type in a parameter pack
template<typename... Ts>
struct last_type_of
{
using type = typename nth_type_of<sizeof...(Ts) - 1, Ts...>::type;
};
//===============================================================================
// FUNCTIONS FOR EXTRACTING THE n-th VALUE OF AN ARGUMENT PACK
// Base step
template<int I, typename T, typename... Ts>
auto nth_value_of(T&& t, Ts&&... args) ->
typename std::enable_if<(I == 0), decltype(std::forward<T>(t))>::type
{
return std::forward<T>(t);
}
// Induction step
template<int I, typename T, typename... Ts>
auto nth_value_of(T&& t, Ts&&... args) ->
typename std::enable_if<(I > 0), decltype(
std::forward<typename nth_type_of<I, T, Ts...>::type>(
std::declval<typename nth_type_of<I, T, Ts...>::type>()
)
)>::type
{
using return_type = typename nth_type_of<I, T, Ts...>::type;
return std::forward<return_type>(nth_value_of<I - 1>((std::forward<Ts>(args))...));
}
// Helper function for retrieving the first value of an argument pack
template<typename... Ts>
auto first_value_of(Ts&&... args) ->
decltype(
std::forward<typename first_type_of<Ts...>::type>(
std::declval<typename first_type_of<Ts...>::type>()
)
)
{
using return_type = typename first_type_of<Ts...>::type;
return std::forward<return_type>(nth_value_of<0>((std::forward<Ts>(args))...));
}
// Helper function for retrieving the last value of an argument pack
template<typename... Ts>
auto last_value_of(Ts&&... args) ->
decltype(
std::forward<typename last_type_of<Ts...>::type>(
std::declval<typename last_type_of<Ts...>::type>()
)
)
{
using return_type = typename last_type_of<Ts...>::type;
return std::forward<return_type>(nth_value_of<sizeof...(Ts) - 1>((std::forward<Ts>(args))...));
}
//===============================================================================
// METAFUNCTION FOR COMPUTING THE UNDERLYING TYPE OF HOMOGENEOUS PARAMETER PACKS
// Used as the underlying type of non-homogeneous parameter packs
struct null_type
{
};
// Declare primary template
template<typename... Ts>
struct homogeneous_type;
// Base step
template<typename T>
struct homogeneous_type<T>
{
using type = T;
static const bool isHomogeneous = true;
};
// Induction step
template<typename T, typename... Ts>
struct homogeneous_type<T, Ts...>
{
// The underlying type of the tail of the parameter pack
using type_of_remaining_parameters = typename homogeneous_type<Ts...>::type;
// True if each parameter in the pack has the same type
static const bool isHomogeneous = std::is_same<T, type_of_remaining_parameters>::value;
// If isHomogeneous is "false", the underlying type is the fictitious null_type
using type = typename std::conditional<isHomogeneous, T, null_type>::type;
};
// Meta-function to determine if a parameter pack is homogeneous
template<typename... Ts>
struct is_homogeneous_pack
{
static const bool value = homogeneous_type<Ts...>::isHomogeneous;
};
//===============================================================================
// META-FUNCTIONS FOR CREATING INDEX LISTS
// The structure that encapsulates index lists
template <unsigned... Is>
struct index_list
{
};
// Collects internal details for generating index ranges [MIN, MAX)
namespace detail
{
// Declare primary template for index range builder
template <unsigned MIN, unsigned N, unsigned... Is>
struct range_builder;
// Base step
template <unsigned MIN, unsigned... Is>
struct range_builder<MIN, MIN, Is...>
{
typedef index_list<Is...> type;
};
// Induction step
template <unsigned MIN, unsigned N, unsigned... Is>
struct range_builder : public range_builder<MIN, N - 1, N - 1, Is...>
{
};
}
// Meta-function that returns a [MIN, MAX) index range
template<unsigned MIN, unsigned MAX>
using index_range = typename detail::range_builder<MIN, MAX>::type;
//===============================================================================
// CLASSES AND FUNCTIONS FOR REALIZING LOOPS ON ARGUMENT PACKS
// Implementation inspired by @jogojapan's answer to this question:
// http://stackoverflow.com/questions/14089637/return-several-arguments-for-another-function-by-a-single-function
// Collects internal details for implementing functor invocation
namespace detail
{
// Functor invocation is realized through variadic inheritance.
// The constructor of each base class invokes an input functor.
// An functor invoker for an argument pack has one base class
// for each argument in the pack
// Realizes the invocation of the functor for one parameter
template<unsigned I, typename T>
struct invoker_base
{
template<typename F, typename U>
invoker_base(F&& f, U&& u) { f(u); }
};
// Necessary because a class cannot inherit the same class twice
template<unsigned I, typename T>
struct indexed_type
{
static const unsigned int index = I;
using type = T;
};
// The functor invoker: inherits from a list of base classes.
// The constructor of each of these classes invokes the input
// functor with one of the arguments in the pack.
template<typename... Ts>
struct invoker : public invoker_base<Ts::index, typename Ts::type>...
{
template<typename F, typename... Us>
invoker(F&& f, Us&&... args)
:
invoker_base<Ts::index, typename Ts::type>(std::forward<F>(f), std::forward<Us>(args))...
{
}
};
}
// The functor provided in the first argument is invoked for each
// argument in the pack whose index is contained in the index list
// specified in the second argument
template<typename F, unsigned... Is, typename... Ts>
void for_each_in_arg_pack_subset(F&& f, index_list<Is...> const& i, Ts&&... args)
{
// Constructors of invoker's sub-objects will invoke the functor.
// Note that argument types must be paired with numbers because the
// implementation is based on inheritance, and one class cannot
// inherit the same base class twice.
detail::invoker<detail::indexed_type<Is, typename nth_type_of<Is, Ts...>::type>...> invoker(
f,
(nth_value_of<Is>(std::forward<Ts>(args)...))...
);
}
// The functor provided in the first argument is invoked for each
// argument in the pack
template<typename F, typename... Ts>
void for_each_in_arg_pack(F&& f, Ts&&... args)
{
for_each_in_arg_pack_subset(f, index_range<0, sizeof...(Ts)>(), std::forward<Ts>(args)...);
}
// The functor provided in the first argument is given in input the
// arguments in whose index is contained in the index list specified
// as the second argument.
template<typename F, unsigned... Is, typename... Ts>
void forward_subpack(F&& f, index_list<Is...> const& i, Ts&&... args)
{
f((nth_value_of<Is>(std::forward<Ts>(args)...))...);
}
// The functor provided in the first argument is given in input all the
// arguments in the pack.
template<typename F, typename... Ts>
void forward_pack(F&& f, Ts&&... args)
{
f(std::forward<Ts>(args)...);
}
CONCLUSION
Bien sûr, même si j'ai ma propre réponse à cette question (et en fait à cause de ce fait), je suis curieux de savoir si des solutions de rechange ou de meilleures solutions existent qui j'ai manqué - en dehors de ceux mentionnés dans le "Travaille" de la question.