Ceci est un C solution ++ 11 pour le type de problème plus général de l'application une fonction ou foncteur F
, en prenant N
de type T
paramètres et le retour de type Ret
, les N
arguments à des positions successives de certains itérateur d'entrée.
Cette flexibilité gagne plusieurs sur une solution paramétrés par un conteneur-of-T
des arguments: -
Vous pouvez extraire les arguments d'une N
arbitraire plage -sized dans une séquence.
La séquence n'a pas besoin d'être un conteneur de T
- bien que ce soit une séquence de quelque chose convertible en T
.
Vous pouvez extraire les arguments en avant-dernier (comme vous le faites) ou en avant-dernier, à partir des types de conteneur standard ou de ceux prenant en charge les itérateurs directs et inverses. Vous pouvez même appliquer F
aux arguments consommés directement à partir de certains flux d'entrée, sans extraction intermédiaire .
Et bien sûr, vous pouvez changer d'avis sur le type de séquence dans lequel pour fournir des arguments sans avoir à changer la solution de l'application fonctionnelle.
Interface
template<typename Func, typename InIter, typename Stop = std::nullptr_t>
typename function_traits<typename std::decay<Func>::type>::return_type
invoke(Func && f, InIter it, Stop stop = Stop());
Vous pouvez utiliser cela comme:
auto result = invoke(func,iter);
appliquer func
aux arguments à N
positions successives du iterator iter
.
De cette façon, vous n'obtenez aucune vérification de portée pour que les arguments N
soient légitimement accessibles à votre programme à ces positions. Le code de vérification de plage que vous allez trouver dans l'implémentation sera compilé à rien et si vous empiétez sur des limites il y aura UB.
Si vous voulez aller vérifier vous pouvez à la place du code:
auto result = invoke(func,iter,end);
où end
est un itérateur du même type que iter
délimitant l'extrémité de la gamme disponible de la manière habituelle. Dans ce cas, un std::out_of_range
sera envoyé si N
dépasse la taille de la plage.
mise en œuvre
#include <type_traits>
#include <functional>
#include <string>
template<typename T>
struct function_traits;
template <typename Ret, typename ArgT, typename... ArgRest>
struct function_traits<Ret(*)(ArgT, ArgRest...)>
{
static constexpr std::size_t n_args = 1 + sizeof...(ArgRest);
using first_arg_type = ArgT;
using return_type = Ret;
};
template <typename Ret, typename ArgT, typename... ArgRest>
struct function_traits<std::function<Ret(ArgT, ArgRest...)>>
{
static constexpr std::size_t n_args = 1 + sizeof...(ArgRest);
using first_arg_type = ArgT;
using return_type = Ret;
};
namespace detail {
template<typename Left, typename Right>
typename std::enable_if<!std::is_same<Left,Right>::value>::type
range_check(Left, Right, std::string const &){}
template<typename Left, typename Right>
typename std::enable_if<std::is_same<Left,Right>::value>::type
range_check(Left start, Right end, std::string const & gripe) {
if (start == end) {
throw std::out_of_range(gripe);
}
}
template<
std::size_t N, typename Func, typename InIter, typename Stop,
typename ...Ts
>
typename std::enable_if<
N == function_traits<typename std::decay<Func>::type>::n_args,
typename function_traits<typename std::decay<Func>::type>::return_type
>::type
invoke(Func && f, InIter, Stop, Ts...args)
{
return f(args...);
}
template<
std::size_t N, typename Func, typename InIter, typename Stop,
typename ...Ts
>
typename std::enable_if<
N != function_traits<typename std::decay<Func>::type>::n_args,
typename function_traits<typename std::decay<Func>::type>::return_type
>::type
invoke(Func && f, InIter it, Stop stop, Ts...args)
{
range_check(it,stop,
"Function takes more arguments than are available "
"in `" + std::string(__PRETTY_FUNCTION__) + '`');
using arg_type = typename
function_traits<typename std::decay<Func>::type>::first_arg_type;
auto arg = static_cast<arg_type>(*it);
return invoke<N + 1>(std::forward<Func>(f),++it,stop,args...,arg);
}
} // namespace detail
template<typename Func, typename InIter, typename Stop = std::nullptr_t>
typename function_traits<typename std::decay<Func>::type>::return_type
invoke(Func && f, InIter it, Stop stop = Stop())
{
return detail::invoke<0>(std::forward<Func>(f),it,stop);
}
Les deux spécialisations de function_traits<T>
fournies limiteront compilation aux types fonctionnels T
qui prennent au moins un argument qui devrait suffire pour les applications probables. Si vous avez besoin de soutenir appel sur les types prenant 0 arguments, vous pouvez les augmenter avec:
template <typename Ret>
struct function_traits<Ret(*)()>
{
static constexpr std::size_t n_args = 0;
using return_type = Ret;
};
template <typename Ret>
struct function_traits<std::function<Ret()>>
{
static constexpr std::size_t n_args = 0;
using return_type = Ret;
};
La spécialisation des fonctions libres function_traits<Ret(*)(ArgT, ArgRest...)>
, est strictement une commodité redondante, car ils pourraient aussi être enveloppés dans std::function
objets , comme vous êtes obligé de faire pour tout ce qui est plus fantaisiste qu'une fonction gratuite.
Démo
Pour un programme qui exerce les fonctions discutées vous pouvez ajouter:
#include <iostream>
#include <list>
#include <vector>
#include <deque>
#include <sstream>
#include <iterator>
struct num
{
double d;
explicit operator double() const {
return d;
}
};
double add4(double d0, double d1, double d2, double d3)
{
std::cout << d0 << '+' << d1 << '+' << d2 << '+' << d3 << "\n=";
return d0 + d1 + d2 + d3;
}
int multiply2(int i0, int i1)
{
std::cout << i0 << '*' << i1 << "\n=";
return i0 * i1;
}
struct S
{
int subtract3(int i0, int i1, int i2) const
{
std::cout << i0 << '-' << i1 << '-' << i2 << "\n=";
return i0 - i1 - i2;
}
int compute(std::list<int> const & li) const {
std::function<int(int,int,int)> bind = [this](int i0, int i1, int i2) {
return this->subtract3(i0,i1,i2);
};
return invoke(bind,li.begin());
}
};
int main()
{
std::vector<double> vd{1.0,2.0,3.0,4.0};
std::vector<double> vdshort{9.0};
std::list<int> li{5,6,7,8};
std::deque<num> dn{num{10.0},num{20.0},num{30.0},num{40.0}};
std::istringstream iss{std::string{"10 9 8"}};
std::istream_iterator<int> it(iss);
std::cout << invoke(add4,vd.rbegin()) << '\n';
std::cout << invoke(multiply2,li.begin()) << '\n';
std::cout << invoke(add4,dn.rbegin()) << '\n';
std::cout << invoke(multiply2,++it) << '\n';
S s;
std::cout << '=' << s.compute(li) << '\n';
try {
std::cout << invoke(add4,vdshort.begin(),vdshort.end()) << '\n';
} catch(std::out_of_range const & gripe) {
std::cout << "Oops :(\n" << gripe.what() << '\n';
}
return 0;
}
Le cas de:
S s;
std::cout << '=' << s.compute(li) << '\n';
est particulièrement pertinente à votre problème particulier, étant donné que ici, nous appelons S::compute(std::list<int> const & li)
pour appliquer une autre méthode non statique de S
aux arguments livrés dans la liste li
. Voir dans la mise en œuvre de S::compute
comment l'utilisation d'un lambda peut commodément se lier à la fois l'objet appelant S
et S::compute
dans un std::function
nous pouvons passer à invoke
.
Live demo
'compute()' ne connaît pas la taille de 'pars', ces règles à peu près hors métaprogrammation. P.S .: sauf si vous aimez copier 'list's tout le temps sans raison valable,' compute() 'devrait prendre son paramètre par référence. –
La taille des pars est-elle connue à la compilation? –
@VittorioRomeo: ce n'est pas le cas. La taille est connue en temps courant. Cependant, il pourrait être déduit dans le temps de compilation du nombre de paramètres qui recevront 'calcul (x1, x2, .., xn)' – lrleon