Ableitung von STD :: Funktion mit mehr als zwei Argumenten
-
27-10-2019 - |
Frage
ich wundere mich warum std::function
ist sich nur der Zwei-Argument-Funktionen bewusst. Ich habe einen Code geschrieben, der gut funktioniert, aber es gibt eine Reihe von Einschränkungen. Jedes Feedback willkommen. Insbesondere vermute ich, dass ich das Rad neu erfinde.
Mein Code ist eingeschaltet Ideone Und ich werde mich darauf beziehen.
Zum Beispiel kann ich die Art von beschreiben main
mit:
function_type_deducer(main).describe_me();
// Output: I return i and I take 2 arguments. They are of type: i PPc
(Wo 'i' 'int' und 'ppc' bedeutet Zeiger-auf-Zeiger-zu-Char))
Standard std::function
Funktioniert nicht mit Funktionen mit mehr als zwei Argumenten (siehe die letzten beiden Zeilen meines Codes), aber dieser Code (der Beispielcode zeigt drei Arg-Funktionen). Vielleicht sollte mein Design stattdessen in der Standardbibliothek verwendet werden! Ich definiere typedef tuple<Args...> args_as_tuple;
Alle Argumente speichern, nicht nur die ersten beiden Argumententypen.
Der Haupttrick ist der Abzug in dieser Funktion:
template<class T, class... Args>
auto function_type_deducer(T(Args...)) -> Function__<T, Args...> {
return Function__<T, Args...> {};
}
Einschränkungen:
- Es funktioniert nicht mit Lambdas. Dies wird nicht kompilieren
function_type_deducer([](){}).describe_me();
- Es bemerkt nicht, dass es einen kleinen Unterschied zwischen gibt
x
undy
, wiey
nimmt einstring&
, wox
nimmt einstring
. (STD :: Funktion bemerkt dies auch nicht)
Irgendwelche Ideen, wie man eines davon repariert? Habe ich das Rad neu erfunden?
Lösung
Dies wird nicht kompilieren
function_type_deducer([](){}).describe_me();
Es würde funktionieren, wenn function_type_deducer
war keine Vorlage. :) Nicht kaufende Lambdas (leer []
) sind implizit umwandelbar zu Funktionszeiger. Leider werden implizite Konvertierungen für einen Abzug von Vorlagenargumenten nicht berücksichtigt. Sehen diese Frage Für weitere Informationen (Beachten Sie, dass meine Antwort nicht vollständig korrekt ist, wie die Kommentare angeben).
Es bemerkt nicht, dass es einen kleinen Unterschied zwischen x und y gibt, da y eine Zeichenfolge nimmt &, wobei X eine Zeichenfolge nimmt.
Das ist kein Problem mit der Funktion, das ist ein Problem mit typeid
, wie dieser einfache Testcode zeigt:
template<class T>
void x(void(T)){
T v;
(void)v;
}
void f1(int){}
void f2(int&){}
int main(){
x(f1);
x(f2);
}
Live -Beispiel auf ideone. Ausgabe:
Fehler: 'V' als Referenz deklariert, aber nicht initialisiert
Eine einfache Lösung kann das Tag -Versand verwenden:
#include <type_traits> // is_reference
#include <iostream>
#include <typeinfo>
template<class T>
void print_name(std::true_type){
std::cout << "reference to " << typeid(T).name();
}
template<class T>
void print_name(std::false_type){
std::cout << typeid(T).name();
}
template<class T>
void print_name(){
print_name(typename std::is_reference<T>::type());
}
Und Ruf an print_name<NextArg>()
Anstatt von typeid(NextArg).name()
.
Habe ich das Rad neu erfunden?
Ja, irgendwie und nein, du hast es nicht getan. Boost.Funktion Bietet Typedefs für alle Argumente (argN_type
Stil), so wie die statische Konstante arity
für die Anzahl davon. Sie können jedoch nicht einfach auf diese Typedefs zugreifen. Sie würden einen Kreisverkehr benötigen, um nicht versehentlich auf nicht existierende zuzugreifen. Das tuple
Idee funktioniert am besten, kann jedoch auf schönere Weise geschrieben werden. Hier ist eine modifizierte Version von etwas, das ich einmal geschrieben habe:
#include <tuple>
#include <type_traits>
#include <iostream>
#include <typeinfo>
namespace detail{
template<class T>
std::ostream& print_name(std::ostream& os);
template<class T>
std::ostream& print_pointer(std::ostream& os, std::true_type){
typedef typename std::remove_pointer<T>:: type np_type;
os << "pointer to ";
return print_name<np_type>(os);
}
template<class T>
std::ostream& print_pointer(std::ostream& os, std::false_type){
return os << typeid(T).name();
}
template<class T>
std::ostream& print_name(std::ostream& os, std::true_type){
return os << "reference to " << typeid(T).name();
}
template<class T>
std::ostream& print_name(std::ostream& os, std::false_type){
return print_pointer<T>(os, typename std::is_pointer<T>::type());
}
template<class T>
std::ostream& print_name(std::ostream& os){
return print_name<T>(os, typename std::is_reference<T>::type());
}
// to workaround partial function specialization
template<unsigned> struct int2type{};
template<class Tuple, unsigned I>
std::ostream& print_types(std::ostream& os, int2type<I>){
typedef typename std::tuple_element<I,Tuple>::type type;
print_types<Tuple>(os, int2type<I-1>()); // left-folding
os << ", ";
return print_name<type>(os);
}
template<class Tuple>
std::ostream& print_types(std::ostream& os, int2type<0>){
typedef typename std::tuple_element<0,Tuple>::type type;
return print_name<type>(os);
}
} // detail::
template<class R, class... Args>
struct function_info{
typedef R result_type;
typedef std::tuple<Args...> argument_tuple;
static unsigned const arity = sizeof...(Args);
void describe_me(std::ostream& os = std::cout) const{
using namespace detail;
os << "I return '"; print_name<result_type>(os);
os << "' and I take '" << arity << "' arguments. They are: \n\t'";
print_types<argument_tuple>(os, int2type<arity-1>()) << "'\n";
}
};
Live -Beispiel auf ideone. Ausgabe:
main: I return 'i' and I take '2' arguments. They are:
'i, pointer to pointer to c'
x: I return 'Ss' and I take '3' arguments. They are:
'i, Ss, c'
y: I return 'Ss' and I take '3' arguments. They are:
'i, reference to Ss, c'
Andere Tipps
Der Link zur Antwort mit der Lambda -Funktion bietet den kritischen Hinweis: Sie müssen einen Zeiger auf eine Mitgliedsfunktion für den Funktionsaufrufbetreiber erhalten. Das heißt, wenn T
ist ein Funktionsobjekt, das Sie betrachten müssen &T::operator()
. Mit verallgemeinerter SFINAE können Sie feststellen, ob dieser Funktionsanruf -Operator vorliegt. Wenn Sie dieses Zeug möglicherweise auf eine Art und Weise zusammenstellen, ergibt sich dies (die mit einer aktuellen Version von GCC und Clang zusammenfasst, mit Ausnahme der Lambda -Funktion, die von Clang noch nicht unterstützt wird):
#include <iostream>
#include <sstream>
#include <string>
#include <typeinfo>
#include <functional>
#include <utility>
// -----------------------------------------------------------------------------
struct S {
void f(int, std::string&, void (*)(int)) {}
void g(int, std::string&, void (*)(int)) const {}
};
// -----------------------------------------------------------------------------
template <typename T> struct describer;
template <> struct describer<S>;
template <> struct describer<int>;
template <> struct describer<void>;
template <> struct describer<std::string>;
template <typename T> struct describer<T&>;
template <typename T> struct describer<T*>;
template <typename T> struct describer<T const>;
template <typename T> struct describer<T volatile>;
template <typename T> struct describer<T const volatile>;
template <typename T, int Size> struct describer<T(&)[Size]>;
template <typename T> struct describer {
static std::string type() { return "???"; }
};
template <> struct describer<S> {
static std::string type() { return "S"; }
};
template <> struct describer<void> {
static std::string type() { return "void"; }
};
template <> struct describer<int> {
static std::string type() { return "int"; }
};
template <> struct describer<std::string> {
static std::string type() { return "std::string"; }
};
template <typename T> struct describer<T&> {
static std::string type() { return describer<T>::type() + std::string("&"); }
};
template <typename T> struct describer<T&&> {
static std::string type() { return describer<T>::type() + std::string("&&"); }
};
template <typename T> struct describer<T*> {
static std::string type() { return describer<T>::type() + std::string("&"); }
};
template <typename T> struct describer<T const> {
static std::string type() { return describer<T>::type() + std::string(" const"); }
};
template <typename T> struct describer<T volatile> {
static std::string type() { return describer<T>::type() + std::string(" volatile"); }
};
template <typename T> struct describer<T const volatile> {
static std::string type() { return describer<T>::type() + std::string(" const volatile"); }
};
template <typename T, int Size> struct describer<T(&)[Size]>
{
static std::string type() {
std::ostringstream out;
out << "(array of " << Size << " " << describer<T>::type() << " objects)&";
return out.str();
}
};
template <typename... T> struct description_list;
template <> struct description_list<> { static std::string type() { return std::string(); } };
template <typename T> struct description_list<T> { static std::string type() { return describer<T>::type(); } };
template <typename T, typename... S> struct description_list<T, S...> {
static std::string type() { return describer<T>::type() + ", " + description_list<S...>::type(); }
};
template <typename R, typename... A>
struct describer<R(*)(A...)>
{
static std::string type() {
return "pointer function returning " + describer<R>::type() + " and taking arguments"
+ "(" + description_list<A...>::type() + ")";
}
};
template <typename R, typename S, typename... A>
struct describer<R(S::*)(A...)>
{
static std::string type() {
return "pointer to member function of " + describer<S>::type() + " returning " + describer<R>::type() + " "
"and taking arguments" + "(" + description_list<A...>::type() + ")";
}
};
template <typename R, typename S, typename... A>
struct describer<R(S::*)(A...) const>
{
static std::string type() {
return "pointer to const member function of " + describer<S>::type() + " returning " + describer<R>::type() + " "
"and taking arguments" + "(" + description_list<A...>::type() + ")";
}
};
template <typename T> char (&call_op(decltype(&T::operator())*))[1];
template <typename T> char (&call_op(...))[2];
template <typename T> struct has_function_call_operator { enum { value = sizeof(call_op<T>(0)) == 1 }; };
template <typename T>
typename std::enable_if<!has_function_call_operator<T>::value>::type describe(std::string const& what, T)
{
std::cout << "describe(" << what << ")=" << describer<T>::type() << "\n";
}
template <typename T>
typename std::enable_if<has_function_call_operator<T>::value>::type describe(std::string const& what, T)
{
std::cout << "describe(" << what << ")=function object: " << describer<decltype(&T::operator())>::type() << "\n";
}
int f(std::string, std::string const&, std::string&&) { return 0; }
int g(std::string&, std::string const&) { return 0; }
int main()
{
describe("int", 1);
describe("f", &f);
describe("g", &g);
describe("S::f", &S::f);
describe("S::g", &S::g);
describe("mini-lambda", []{}); // doesn't work with clang, yet.
describe("std::function<int(int(&)[1], int(&)[2], int(&)[4], int(&)[4])>",
std::function<int(int(&)[1], int(&)[2], int(&)[4], int(&)[4])>());
}