接受任何类型的可调用并且知道参数类型

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::functions,以及只接受 T(对于 lambdas / 函数对象)的重载。最后一个不允许您推断参数类型(例如通用 lambda),但您可以尝试在 T 中找到一个 operator() 并推断其参数类型(只有其中一个才有可能) . @dyp,很公平,但我觉得这还不够好,因为这迫使我指定参数“两次”,一次是在期权调用中,一次是在 Lamba 中。我打算在图书馆中使用它,我知道如果这不是“正常工作”,我会感到困惑。那么我们可以做得更好吗? Here's 一个提升版本的如何推断函数对象的参数类型(使用单个 operator())。 struct problemtemplate&lt;typename T&gt;void operator()(T&amp;&amp;t)conststd::cout&lt;&lt;std::forward&lt;T&gt;(t)&lt;&lt;"\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&amp;&amp;(通用参考)。 在标准中有这个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

我可以开发一个 SQL/CLR 函数来接受任何类型的参数,如 MAX() 吗?

查明 C++ 对象是不是可调用

将 varargs 参数限制为某些分离类型

如何创建一个从管道和命令行接受多种参数类型的函数?