Question

Je me demande pourquoi std::function est seulement au courant de deux arguments les fonctions. J'ai écrit un code qui fonctionne bien, mais il y a un certain nombre de limites. Tout accueil commentaires. En particulier, je pense que je suis réinventer la roue.

Mon code est ideone et je l'appellerai.

Par exemple, je peux décrire le type de main avec:

function_type_deducer(main).describe_me();
// Output: I return i and I take 2 arguments.  They are of type:  i PPc

(où 'i' signifie 'int' et signifie 'PPc' pointeur à pointer à char)

std::function standard ne fonctionne pas avec des fonctions avec plus de deux args (voir les deux dernières lignes de mon code), mais ce code ne (le code exemple montre trois fonctions arg). Peut-être que ma conception doit être utilisé dans la bibliothèque standard à la place! Je définis typedef tuple<Args...> args_as_tuple; pour stocker tous args, pas seulement les deux premiers types d'arguments.

La principale astuce est la déduction dans cette fonction:

template<class T, class... Args>
auto function_type_deducer(T(Args...)) -> Function__<T, Args...> {
        return Function__<T, Args...> {};
}

Limitations:

  • Il ne fonctionne pas avec lambdas. Cela ne compilera pas function_type_deducer([](){}).describe_me();
  • Il ne remarque pas qu'il ya une petite différence entre x et y, comme y prend string&, où x prend string. (Std :: fonction ne remarque pas que ce soit)

Toutes les idées sur la façon de résoudre l'un de ces? Ai-je réinventé la roue?

Était-ce utile?

La solution

Ce ne compilera pas function_type_deducer([](){}).describe_me();

Il fonctionnerait si function_type_deducer était pas un modèle. :) lambdas non-capture ([] vide) sont implicitement convertibles en pointeurs de fonction. Malheureusement, les conversions implicites ne sont pas pris en considération pour une déduction des arguments de modèle. Voir cette question pour plus d'informations (notez que ma réponse est pas tout à fait correctes, les commentaires indiquent).


Il ne remarque pas qu'il ya une petite différence entre x et y, comme y prend une chaîne et où x prend une chaîne.

Ce n'est pas un problème avec la fonction, qui est un problème avec typeid, comme ce simple montre de TestCode:

template<class T>
void x(void(T)){
    T v;
    (void)v;
}

void f1(int){}
void f2(int&){}

int main(){
    x(f1);
    x(f2);
}

exemple en direct sur Ideone. Sortie:

erreur: « v » déclarée comme référence, mais pas initialisées

Une solution simple pourrait être en utilisant dispatching tag:

#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());
}

appel print_name<NextArg>() au lieu de typeid(NextArg).name().


Ai-je réinventé la roue?

Oui, en quelque sorte et non, vous ne l'avez pas. Boost.Function fournit typedefs pour tous les arguments (style argN_type), aswell comme arity statique constante du nombre de ceux-ci. Cependant, vous ne pouvez pas facilement accéder à ces typedefs génériquement. Vous auriez besoin d'une manière détournée de ne pas les accès accidentially non Existant. L'idée de tuple fonctionne le mieux, mais il peut être écrit d'une manière plus agréable. Voici une version modifiée de quelque chose que j'ai écrit:

#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";
  }
};

exemple en direct sur Ideone. Sortie:

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'

Autres conseils

Le lien vers la réponse avec la fonction lambda fournit l'indice critique: vous devez obtenir un pointeur vers une fonction membre pour l'opérateur d'appel de fonction. Autrement dit, si T est un objet de fonction, vous avez besoin de regarder &T::operator(). Avec SFINAE généralisée, vous pouvez déterminer si cet opérateur d'appel de fonction existe. Mettre ce genre de choses ensemble dans un rond-point peut-être de manière quelque peu, donne ce (qui compile avec une version récente par clang de gcc et clang, à l'exception de la fonction lambda qui n'est pas pris en charge, encore,):

#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])>());
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top