Вывод std :: function с более чем двумя аргументами - PullRequest
3 голосов
/ 13 января 2012

Интересно, почему std::function знает только о функциях с двумя аргументами.Я написал некоторый код, который работает хорошо, но есть ряд ограничений.Любые отзывы приветствуются.В частности, я подозреваю, что я заново изобретаю колесо.

Мой код на ideone , и я буду ссылаться на него.

Например, я могу описать тип main с помощью:

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

(где «i» означает «int», а «PPc» означает «указатель на указатель на символ»)

Стандарт std::function не работает с функциями с более чем двумя аргументами (см.последние две строки моего кода), но этот код делает (пример кода демонстрирует функции с тремя аргументами).Может быть, мой дизайн следует использовать вместо стандартной библиотеки!Я определяю typedef tuple<Args...> args_as_tuple; для хранения всех аргументов, а не только первых двух типов аргументов.

Основная хитрость заключается в удержании этой функции:

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

Ограничения:

  • Это не работает с лямбдами.Это не скомпилирует function_type_deducer([](){}).describe_me();
  • Не замечает, что есть небольшая разница между x и y, так как y занимает string&, где x занимаетstring.(std :: function этого тоже не замечает)

Есть идеи, как это исправить?Я заново изобрел колесо?

Ответы [ 2 ]

5 голосов
/ 13 января 2012

Это не скомпилируется function_type_deducer([](){}).describe_me();

Это сработало бы, если бы function_type_deducer не был шаблоном. :) Не захватывающие лямбды (пустые []) неявно преобразуются в указатели функций. К сожалению, неявные преобразования не принимаются во внимание при выводе аргументов некоторого шаблона. См. этот вопрос для получения дополнительной информации (обратите внимание, что мой ответ не совсем правильный, как указывают комментарии).


Он не замечает, что между x и y есть небольшая разница, поскольку y принимает строку &, где x принимает строку.

Это не проблема с функцией, это проблема с typeid, как показывает этот простой тестовый код:

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

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

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

Живой пример на Ideone . Выход:

ошибка: 'v' объявлена ​​как ссылка, но не инициализирована

Простым исправлением может быть использование диспетчеризации тегов:

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

И позвоните print_name<NextArg>() вместо typeid(NextArg).name().


Я заново изобрел колесо?

Да, вроде как и нет, вы этого не сделали. Boost.Function предоставляет typedefs для всех аргументов (стиль argN_type), а также статическую константу arity для их числа. Тем не менее, вы не можете легко получить доступ к этим typedefs в общем. Вам понадобится окольный способ, чтобы случайно не получить доступ к несуществующим. Идея tuple работает лучше всего, однако она может быть написана лучше. Вот модифицированная версия того, что я однажды написал:

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

Живой пример на Ideone . Выход:

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'
1 голос
/ 13 января 2012

Ссылка на ответ с помощью лямбда-функции предоставляет критическую подсказку: вам нужно получить указатель на функцию-член для оператора вызова функции. То есть, если T является функциональным объектом, вам нужно посмотреть на &T::operator(). С обобщенным SFINAE вы можете определить, существует ли этот оператор вызова функции. Объединение этих вещей, возможно, несколько окольными путями, приводит к этому (который компилируется с недавними версиями gcc и clang, за исключением функции лямбда, которая пока не поддерживается clang):

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