C 从函数指针推导的转发参数时无隐式转换

C++ no implicit conversions when forwarding arguments deduced from function pointer

本文关键字:参数 转换 转发 函数 指针      更新时间:2023-10-16

我正在尝试产生一个智能的代理函数,以便将参数转发为给定。

这是代码:

#include <utility>
#include <iostream>
void func(int foo) {
    std::cout<<foo<<"n";
}
template<typename... ARGS>
void proxy( void(*callback)(ARGS...), ARGS&&... args) {
    callback( std::forward<ARGS>(args)... );
}
int main() {
    proxy( func, 17 );
    int foo = 17;
    proxy( func, foo );
}

我希望通过让模板分辨率找出func的参数,我将获得签名void proxy( void(*)(int), int );的代理。因此,它应该同时接受第一个调用(int是rvalue(或第二个(int是lvalue(。

实际上,上述程序失败了:

so.cpp:16:5: error: no matching function for call to 'proxy'
    proxy( func, foo );
    ^~~~~
so.cpp:9:6: note: candidate template ignored: deduced conflicting types for parameter 'ARGS' (<int> vs. <int &>)
void proxy( void(*callback)(ARGS...), ARGS&&... args) {
     ^
1 error generated.

即。 - 未能隐式从int &转换为int

我在这里做错了什么?

问题是有两个地方可以从中推断出ARGS。函数模板参数扣除始终是针对每个函数参数/参数对进行的,然后组合的。

[temp.deduct.type]

2在某些情况下,扣除是使用一组 类型PA,在其他情况下,将有一组 相应类型PA。类型扣除是独立完成的 对于每个P/A对,而推论的模板参数值为 然后组合。如果对任何P/A对都无法进行类型扣除,则 或者,对于任何一对,扣除会导致多组 推导值的定推值,或者如果不同的对产生不同的推导 值,或任何模板参数既不推导,也没有 明确指定的模板参数扣除失败。一种类型 type参数仅是从数组绑定中推导的 否则推导。

由于函数参数包包含转发引用,因此它将根据相应参数的值类别推导int&int。虽然功能类型(也发生扣除(只能导致int的扣除。对于LVALUES,这两种扣除额不同意,因此替代失败了。

未能替代不是错误,它只是从候选人集中删除过载。但是就您而言,这是唯一的候选人,因此变成了困难。

因此,您需要使两种扣除独立,这意味着两个包装。这仍然会给我们int&int,但是我们可以添加SFIANE检查,以验证删除参考后的类型。这应该给出所需的行为。

template<typename... ARGS1, typename... ARGS2>
std::enable_if_t<(std::is_same_v<std::decay_t<ARGS1>, std::decay_t<ARGS2>> && ...)>
proxy( void(*callback)(ARGS1...), ARGS2&&... args) {
    callback( std::forward<ARGS2>(args)... );
}

返回类型使用折叠表达式来验证每对参数,但只有在删除了其CV- Qualifiers和参考类型之后(这就是decay_t的目的,除其他方面(。如果支票通过,则存在enable_if_t,并且是void(enable_if的默认::type(。

这里是现场。

但是,如果您决定要支持可转换性,则可以修改上述检查以使用std::is_convertible_v而不是std::is_same_v

不幸的是,Args模板参数的两个位置允许每个变量扩展(以及每个单独的参数(不同类型。

您需要提出参数(例如,args&amp;&amp; ... args(依赖或静态演员。您可以执行一种乏味的方法,我认为它保留了完美的转发,这是最后显示的,因为它漂亮的冗长。

这种方法是我的最爱,因为它在intelisense中表现出色。在某些情况下,这种方法不是"完美的",但是它几乎总是一样,所以也许对您有好处:(:

#include <iostream>
using namespace std;
void func (int foo)
{
    std::cout << foo << "n";
}
template <typename T>
struct Mirror
{
    using type = T;
};
template < typename ... FunctionArgs >
void proxy (void (*callback) (FunctionArgs ...), typename Mirror<FunctionArgs>::type ... args)
{
    callback (args ...);
}
int main ()
{
    proxy (func, 17);
    int foo = 17;
    proxy (func, foo);
}

静态铸造方法:

#include <iostream>
using namespace std;
void func (int foo)
{
    std::cout << foo << "n";
}
template < typename ... FunctionArgs, typename ... UsedArgs >
void proxy (void (*callback) (FunctionArgs ...), UsedArgs && ... args)
{
    callback (static_cast < FunctionArgs > (args) ...);
}
int main ()
{
    proxy (func, 17);
    int foo = 17;
    proxy (func, foo);
}

冗长的方法,我认为这保留了所有完美的转发:

#include <iostream>
using namespace std;
void func (int foo)
{
    std::cout << foo << "n";
}
template <typename T>
struct Mirror
{
    using type = T;
};
template < typename ... FunctionArgs, typename ... UsedArgs >
void proxy (void (*callback) (FunctionArgs ...), UsedArgs && ... args)
{
    callback ([](auto m, auto && a) -> decltype(auto)
    {
        if constexpr (std::is_reference_v<decltype(a)>)
        {
            return std::forward<decltype(a)>(a);
        }
        else
        {
            return static_cast<typename decltype(m)::type>(a);
        }
    }( Mirror<UsedArgs>{}, std::forward<FunctionArgs>(args) ) ... );
}
int main ()
{
    proxy (func, 17);
    int foo = 17;
    proxy (func, foo);
}