转发从函数指针推论得出的参数时,C ++没有隐式转换

时间:2019-07-07 05:14:10

标签: c++ language-lawyer

我正在尝试生成一个智能代理功能,以便按照给定的方式转发参数。

代码如下:

#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是一个右值)或第二次调用(int是一个左值)。

实际上,以上程序失败并显示:

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

我在这里做错什么了吗?

2 个答案:

答案 0 :(得分:3)

问题在于有两个地方可以推论ARGS。始终对每个功能参数/参数对分别进行功能模板参数推导,然后组合。

  

[温度扣除类型]

     

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

由于函数参数包包含转发引用,因此将根据相应参数的值类别推导int&int。虽然函数类型(也可以从中进行推论)只能导致推论int。对于左值,这两个推论不一致,因此替换失败。

替换失败不是错误,它只是从候选集中消除了重载。但是在您的情况下,它是唯一的候选者,因此这将成为一个严重的错误。

因此,您需要使两个推论相互独立,这意味着两包。与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)... );
}

返回类型使用fold表达式来验证每对参数,但是只有在这两个参数都删除了cv限定符和引用类型之后(decay_t的作用是这样)。如果检查通过,则enable_if_t存在且为void::type的默认enable_if)。

这里是live

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

答案 1 :(得分:1)

不幸的是,ARGS模板参数的两个位置允许每个可变参数扩展(和每个参数)使用不同的类型。

您需要使参数(例如ARGS && ... 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);
}