简体   繁体   English

使用两个以上的arg推导std :: function

[英]Deducing std::function with more than two args

I wonder why std::function is only aware of two-argument functions. 我想知道为什么std::function只知道两个参数的函数。 I've written some code which is working well, but there are a number of limitations. 我写了一些运行良好的代码,但是有很多限制。 Any feedback welcome. 任何反馈欢迎。 In particular, I suspect I'm reinventing the wheel. 特别是,我怀疑我正在重新发明轮子。

My code is on ideone and I will refer to it. 我的代码是关于ideone的 ,我将参考它。

For example, I can describe the type of main with: 例如,我可以用以下内容描述main的类型:

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

(where 'i' means 'int' and 'PPc' means pointer-to-pointer-to-char) (其中'i'表示'int','PPc'表示指向指针指向char的指针)

Standard std::function doesn't work with functions with more than two args (see the last two lines of my code), but this code does (the sample code demonstrates three-arg functions). 标准std::function不适用于具有两个以上args的函数(请参阅我的代码的最后两行),但此代码可以使用(示例代码演示了三个arg函数)。 Maybe my design should be used in the standard library instead! 也许我的设计应该在标准库中使用! I define typedef tuple<Args...> args_as_tuple; 我定义了typedef tuple<Args...> args_as_tuple; to store all args, not just the first two argument types. 存储所有args,而不仅仅是前两个参数类型。

The main trick is the deduction in this function: 主要技巧是这个函数的推论:

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

Limitations: 限制:

  • It doesn't work with lambdas. 它不适用于lambdas。 This won't compile function_type_deducer([](){}).describe_me(); 这不会编译function_type_deducer([](){}).describe_me();
  • It doesn't notice that there is a small difference between x and y , as y takes a string& , where x takes a string . 它没有注意到xy之间存在小的差异,因为y采用string& ,其中x表示string (std::function doesn't notice this either) (std :: function也没有注意到这一点)

Any ideas on how to fix either of these? 关于如何修复其中任何一个的任何想法? Have I reinvented the wheel? 我重新改造了车轮吗?

This won't compile function_type_deducer([](){}).describe_me(); 这不会编译function_type_deducer([](){}).describe_me();

It would work if function_type_deducer wasn't a template. 如果function_type_deducer不是模板,它将起作用。 :) Non-capturing lambdas (empty [] ) are implicitly convertible to function pointers. :)非捕获lambdas(empty [] )可以隐式转换为函数指针。 Sadly, implicit conversions aren't taken into consideration for some template argument deduction. 遗憾的是,对于某些模板参数推导,不考虑隐式转换。 See this question for more information (note that my answer isn't completely correct as the comments indicate). 有关详细信息,请参阅此问题 (请注意,我的答案并不完全正确,如评论所示)。


It doesn't notice that there is a small difference between x and y, as y takes a string&, where x takes a string. 它没有注意到x和y之间存在小的差异,因为y采用字符串&,其中x表示字符串。

That's not a problem with the function, that's a problem with typeid , as this simple testcode shows: 这不是函数的问题,这是typeid的一个问题,因为这个简单的测试代码显示:

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

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

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

Live example on Ideone . Ideone上的实例 Output: 输出:

error: 'v' declared as reference but not initialized 错误:'v'声明为引用但未初始化

A simple fix might be using tag dispatching: 一个简单的修复可能是使用标签分派:

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

And call print_name<NextArg>() instead of typeid(NextArg).name() . 并调用print_name<NextArg>()而不是typeid(NextArg).name()


Have I reinvented the wheel? 我重新改造了车轮吗?

Yes, kind of and no, you didn't. 是的,有点和不,你没有。 Boost.Function provides typedefs for all arguments ( argN_type style), aswell as the static constant arity for the number thereof. Boost.Function为所有参数( argN_type样式)提供typedef,以及其数量的静态常量arity However, you can't easily access those typedefs generically. 但是,您无法轻松访问这些typedef。 You would need a roundabout way to not accidentially access non-existant ones. 你需要一种迂回的方式来不意外地访问不存在的方法。 The tuple idea works best, however it can be written in a nicer way. tuple想法效果最好,但它可以用更好的方式编写。 Here's a modified version of something I once wrote: 这是我曾写过的东西的修改版本:

#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 example on Ideone . Ideone上的实例 Output: 输出:

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'

The link to the answer with the lambda function provides the critical hint: you need to obtain a pointer to a member function for the function call operator. 使用lambda函数的答案链接提供了关键提示:您需要获取指向函数调用操作符的成员函数的指针。 That is, if T is a function object, you need to look at &T::operator() . 也就是说,如果T是一个函数对象,则需要查看&T::operator() With generalized SFINAE you can determine if this function call operator exists. 使用广义SFINAE,您可以确定此函数调用运算符是否存在。 Putting this stuff together in a possibly somewhat roundabout way, yields this (which compiles with a recent version of gcc and clang, except for the lambda function which isn't supported, yet, by clang): 将这些东西放在一起可能有点迂回的方式,产生这个(用最新版本的gcc和clang编译,除了lambda函数,不支持,但是,通过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])>());
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM