推导具有两个以上参数的std::函数
Deducing std::function with more than two args
我想知道为什么std::function
只知道两个参数函数。我已经编写了一些运行良好的代码,但也有一些局限性。欢迎任何反馈。特别是,我怀疑我在重新发明轮子。
我的代码是表意的,我会参考它。
例如,我可以用来描述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"表示指向char的指针)
标准std::function
不适用于包含两个以上arg的函数(请参阅我的代码的最后两行),但此代码适用(示例代码演示了三个arg函数)。也许我的设计应该在标准库中使用!我定义typedef tuple<Args...> args_as_tuple;
来存储所有参数,而不仅仅是前两种参数类型。
主要技巧是这个函数中的推导:
template<class T, class... Args>
auto function_type_deducer(T(Args...)) -> Function__<T, Args...> {
return Function__<T, Args...> {};
}
限制:
- 它不适用于lambdas。这不会编译
function_type_deducer([](){}).describe_me();
- 它没有注意到
x
和y
之间有很小的差异,因为y
取string&
,其中x
取string
。(std::function也没有注意到这一点)
关于如何解决这两个问题,有什么想法吗?我重新发明轮子了吗?
这不会编译
function_type_deducer([](){}).describe_me();
如果function_type_deducer
不是一个模板,它就会起作用。:)非捕获Lambda(空[]
)可以隐式转换为函数指针。遗憾的是,在一些模板论证推导中没有考虑隐含转换。请参阅此问题以了解更多信息(请注意,正如评论所示,我的回答并不完全正确)。
它没有注意到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为所有参数提供typedef(argN_type
样式),并为其数量提供静态常量arity
。然而,您不能简单地通用地访问这些typedef。你需要一种迂回的方式来避免意外地访问不存在的东西。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: nt'";
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'
lambda函数的答案链接提供了关键提示:您需要为函数调用运算符获取一个指向成员函数的指针。也就是说,如果T
是一个函数对象,则需要查看&T::operator()
。使用广义SFINAE,您可以确定此函数调用运算符是否存在。以一种可能有点迂回的方式将这些东西放在一起,会产生以下结果(它与最新版本的gcc和clang一起编译,除了clang还不支持的lambda函数):
#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])>());
}
- 带有指定长度字符* 参数的 std::regex_search 在 VS2017 中不起作用?
- 为什么 std::function 可以作为 std::not2 的参数?
- 传递给std::function template的template参数究竟代表什么
- 转换函数,将 std::数组的双精度作为参数或双精度作为参数单独转换
- 在C++中,使用带有 std::optional 参数的函数<T>来表示可选参数是否有意义?
- 将函数参数"const char*"转换为"std::string_view"是
- 当指向对象的指针作为参数传递给 std::thread 时,内存可见性
- 为什么 std::绑定错误参数可以成功?
- 使用模板化的键类型定义 std::map,该键类型基于作为参数接收的函数
- std::vector 没有重载函数的实例与参数列表匹配
- std::span<const T> 作为函数模板中的参数
- SegFault 同时使用 std::string::operator+= 和函数作为参数
- 如果模板没有可变参数,则 Lambda 被推导出为 std::function
- 将参数打包的参数传递到 std::queue 中,以便稍后使用不同的函数调用
- 在构造函数中使用可变参数初始化 std::tuple
- 函数对象无法识别它获得的参数(std::set<int>)
- C 11构造函数参数:std ::移动和值或std :: forward and rvalue参考
- std::move 或 std::forward 参数 std::unique_ptr&<T>&
- 错误:不允许使用"C++/ObjC++"使用无效参数"-std=gnu99"
- **编译器错误** - getline() 函数不接受第一个参数"std:ifstream"是什么我的问题?