接受任何类型的可调用并且知道参数类型
Posted
技术标签:
【中文标题】接受任何类型的可调用并且知道参数类型【英文标题】:Accept any kind of callable and also know argument type 【发布时间】:2014-02-23 06:45:17 【问题描述】:我不确定这是否可能,所以这就是我想知道的。
我想创建一个接受任何类型的仿函数/可调用对象的函数,但我想知道参数类型是什么。 (但不强制执行)
所以,这个捕获了所有但没有给我参数的类型:
template < typename T >
void optionA( T );
这个捕获最多,并且有参数的类型
template < typename T >
void optionB( std::function< void(T) > );
但是这个不允许 lambdas,所以
optionB( [](int) );
不会编译。 这有点奇怪,因为这会编译:
std::function< void(int) > func = [](int);
optionB( func );
那么有没有一种方法可以接受所有选项并知道需要哪种类型的参数?
提前致谢!
-- 编辑--
我想这样做的原因是我希望我的库的用户注册一个特定类型的回调。对我来说,最自然的方式是
auto callback = []( int val ) cout << "my callback " << val << endl; ;
object.register( callback );
(使用或不使用回调作为中间变量)
由于我需要根据用户期望的值类型来修改行为,所以我需要知道他/她期望的类型。
【问题讨论】:
"但是这个不允许 lambdas" 它确实允许 lambdas,但是它不能推断出T
。因此,如果您明确指定T
,它应该可以工作。它也不适用于函数名和函数指针。
您可以尝试编写一些重载:对于函数指针,std::function
s,以及只接受 T
(对于 lambdas / 函数对象)的重载。最后一个不允许您推断参数类型(例如通用 lambda),但您可以尝试在 T
中找到一个 operator()
并推断其参数类型(只有其中一个才有可能) .
@dyp,很公平,但我觉得这还不够好,因为这迫使我指定参数“两次”,一次是在期权调用中,一次是在 Lamba 中。我打算在图书馆中使用它,我知道如果这不是“正常工作”,我会感到困惑。那么我们可以做得更好吗?
Here's 一个提升版本的如何推断函数对象的参数类型(使用单个 operator()
)。
struct problemtemplate<typename T>void operator()(T&&t)conststd::cout<<std::forward<T>(t)<<"\n";;
-- 你的optionB
代码应该为这种情况推导出什么类型的T
?它适用于optionA
。泛型函子可以表示整个重载函数集:您的方法假定这不是真的。简而言之,您的optionB
从根本上比optionA
弱,即使您可以让它工作。通常你所要求的不是必需的,所以:你真正想解决什么问题?有类似个问题没有这个问题。描述真正的问题。
【参考方案1】:
这是一个适用于大多数可调用对象的示例,包括仿函数和 lambda(尽管不适用于 泛型仿函数,正如@Yakk 在对该问题的评论中所展示的那样)。
代码在确定返回类型和多个参数时也很有用。
template <typename T>
struct func_traits : public func_traits<decltype(&T::operator())> ;
template <typename C, typename Ret, typename... Args>
struct func_traits<Ret(C::*)(Args...) const>
using result_type = Ret;
template <std::size_t i>
struct arg
using type = typename std::tuple_element<i, std::tuple<Args...>>::type;
;
;
template <typename T>
void option(T&& t)
using traits = func_traits<typename std::decay<T>::type>;
using return_t = typename traits::result_type; // Return type.
using arg0_t = typename traits::template arg<0>::type; // First arg type.
// Output types.
std::cout << "Return type: " << typeid(return_t).name() << std::endl;
std::cout << "Argument type: " << typeid(arg0_t).name() << std::endl;
要添加对常规函数的支持,请添加专门化,例如
template <typename Ret, typename... Args>
struct func_traits<Ret(*)(Args...)> /* ... */
更多有用信息: Is it possible to figure out the parameter type and return type of a lambda?
【讨论】:
我接受这个作为答案,即使它具有相同的 const/mutable/volatile 修饰符问题。由于此方法提供了一种检测参数类型的通用方法,因此它更有用恕我直言。你有什么理由写 decltype(t) 而不是 T ? @Arjan 你说得对,应该只是T
而不是decltype(t)
。 std::decay
仍然是必需的,因为在进行类型推断时,特殊规则适用于 T&&
(通用参考)。
在标准中有这个function_traits
模板会很有用。将任何可调用对象转换为函数的make_function
也会有所帮助。 Here is a proposal.【参考方案2】:
template < typename T >
void option( function< void(T) > )
cout << typeid( T ).name() << endl;
template < typename T >
void option( void (*func)(T) )
option( function< void(T) >( func ) );
template< typename F, typename A >
void wrapper( F &f, void ( F::*func )( A ) const )
option( function< void(A) >( bind( func, f, placeholders::_1 ) ) );
template< typename F, typename A >
void wrapper( F &f, void ( F::*func )( A ) )
option( function< void(A) >( bind( func, f, placeholders::_1 ) ) );
template < typename T >
void option( T t )
wrapper( t, &T::operator() );
void test( int )
struct Object
void operator ()( float )
;
int main( int, char *[] )
Object obj;
option( test );
option( [](double) );
option( obj );
return 0;
基于此处c++0x: overloading on lambda arity 找到的信息,我通过@dyps 链接找到了这些信息
这不是最好的解决方案,因为它需要 const/non-const/volatile 等的重载。 它确实完成了我试图解决的原始问题的工作......
【讨论】:
我认为您可以通过std::is_member_function_pointer
和通用分解特征来实现成员函数重载。
lambda btw 选择函数指针的第二个重载,因为它可以转换为函数指针,但前提是它是无状态的。我建议添加一个只接受T
的通用重载,然后尝试检测operator()
。
o.O 这太糟糕了:你会得到 cv-qualifiers 和 ref-qualifiers 的组合。幸运的是,您只需执行一次:coliru.stacked-crooked.com/a/00750bf7564ab6d4以上是关于接受任何类型的可调用并且知道参数类型的主要内容,如果未能解决你的问题,请参考以下文章
Python 2.7 类型提示 PyCharm 中的可调用类型
“string”不包含“Text”的定义,并且找不到可接受类型为“string”的第一个参数的扩展方法“Te