Dies ist eine C++ 11-Lösung für das allgemeinere Problem Typ F
eine Funktion oder Funktors des Aufbringens unter N
Typ T
Parameter und Zurückkehren Typ Ret
, auf die Argumente N
an aufeinanderfolgenden Positionen einiger InputIterator.
Dies gewinnt mehrere Flexibilitäten über eine Lösung durch ein Container-of- T
der Argumente parametriert: -
Sie die Argumente von einem beliebigen N
innerhalb einer Sequenz -sized Bereich extrahieren können.
Die Sequenz muss kein Container-of-T
sein - obwohl es eine Folge von etwas sein muss, das in T
konvertierbar ist.
Sie können die Argumente entweder vom letzten zum ersten (wie Sie) oder vom ersten zum letzten, aus den Standard-Containertypen extrahieren oder alle, die Vorwärts- und Rückwärts-Iteratoren unterstützen.
Sie können sogar F
auf Argumente anwenden, die direkt von einem Eingabestrom verbraucht werden, ohne Zwischenextraktion.
Und natürlich können Sie Ihre Meinung über die Art der Sequenz ändern, in der Argumente zu liefern, ohne die funktionale Anwendung Lösung zu ändern.
Schnittstelle
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());
können Sie diese wie verwenden:
auto result = invoke(func,iter);
func
bei N
aufeinanderfolgenden Positionen des Iterators iter
auf die Argumente anzuwenden.
Auf diese Weise erhalten Sie keine Bereichsprüfung, N
Argumente sind legitim zugänglich zu Ihrem Programm an diesen Positionen. Der Bereichsüberprüfungscode, den Sie in der Implementierung sehen, kompiliert zu nichts und wenn Sie außerhalb der Grenzen überschreiten, wird es UB geben.
Wenn Sie möchten, reichen Sie Überprüfung stattdessen Code:
auto result = invoke(func,iter,end);
wo end
einen Iterator vom selben Typ ist wie iter
das Ende des verfügbaren Bereichs in der üblichen Weise begrenzen. In diesem Fall wird ein std::out_of_range
ausgelöst, wenn N
die Größe des Bereichs überschreitet.
Implementierung
#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);
}
Die beiden Spezialisierungen von function_traits<T>
wird Kompilierung Funktionstypen T
vorgesehen beschränken, die mindestens ein Argument, das sollte genügen für wahrscheinlich Anwendungen. Sollten Sie benötigen Aufruf auf Typen unterstützen nehmen 0 Argumente dann können Sie sie mit vermehren:
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;
};
Die Spezialisierung kostenlos Funktionen function_traits<Ret(*)(ArgT, ArgRest...)>
, ist ausschließlich eine redundante Bequemlichkeit, da auch sie in std::function
Objekte gewickelt werden konnte , wie Sie für etwas schicker als eine freie Funktion tun müssen.
Demo
Für ein Programm, das die Funktionen besprochen Übungen, die Sie anfügen können:
#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;
}
Der Fall:
S s;
std::cout << '=' << s.compute(li) << '\n';
ist besonders relevant für Ihr spezielles Problem, da Hier rufen wir S::compute(std::list<int> const & li)
, um eine andere nicht-statische Methodeanzuwendenvon S
zu den Argumenten, die in der Liste li
geliefert werden. Siehe in der Umsetzung von S::compute
, wie die Verwendung einer Lambda bequem sowohl die Aufruf S
Objekt und S::compute
in eine std::function
binden können wir können Pass zu invoke
.
Live demo
'compute()' weiß nicht, die Größe des 'pars', diese ziemlich Regeln aus metaprogramming. P.S .: Wenn Sie es nicht mögen, die Liste jederzeit ohne triftigen Grund zu kopieren, sollte compute() 'ihren Parameter als Referenz nehmen. –
Ist die Größe der Pars zur Kompilierzeit bekannt? –
@VittorioRomeo: nicht ist es nicht. Die Größe ist in der Laufzeit bekannt. Aus der Anzahl der Parameter, die 'compute (x1, x2, .., xn) erhalten sollen, kann jedoch in der Kompilierzeit abgeleitet werden.' – lrleon