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 und y, wie y nimmt ein string&, wo x nimmt ein string. (STD :: Funktion bemerkt dies auch nicht)

Irgendwelche Ideen, wie man eines davon repariert? Habe ich das Rad neu erfunden?

War es hilfreich?

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])>());
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top