在从函数指针导出参数时,C++不隐式转换

在从函数指针导出参数时,C++不隐式转换,c++,language-lawyer,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) {

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

代码如下:

#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 );
}
即-未能从int隐式转换为int


我在这里做错了什么吗?

不幸的是,ARGS模板参数允许每个变量展开和每个单独参数使用不同类型的两个位置

您需要进行参数设置,例如ARGS&&。。。参数依赖或静态强制转换。你可以做一个乏味的方法,我相信它能保持完美的转发,这是最后展示的,因为它非常冗长

这种方法是我最喜欢的,因为它与Intelissense的配合非常好。 这种方法在某些情况下并不完美,但它几乎总是一样的,因此可能对您有好处:

#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);
}

不幸的是,ARGS模板参数允许每个变量展开和每个单独参数使用不同类型的两个位置

您需要进行参数设置,例如ARGS&&。。。参数依赖或静态强制转换。你可以做一个乏味的方法,我相信它能保持完美的转发,这是最后展示的,因为它非常冗长

这种方法是我最喜欢的,因为它与Intelissense的配合非常好。 这种方法在某些情况下并不完美,但它几乎总是一样的,因此可能对您有好处:

#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);
}

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

[临时扣除类型]

在某些情况下,使用一组单独的 类型P和A,在其他情况下,将有一组 对应的类型P和A。类型推导独立完成 对于每个P/A对,导出的模板参数值为 然后合并。如果无法对任何P/A对进行类型扣减, 或者,如果对任何一对进行扣减,则会导致多个可能的集合 或如果不同的对产生不同的推导值 值,或者如果任何模板参数既不是推断的,也不是 显式指定,模板参数推断失败。a型 类型参数仅从数组绑定中推导,如果它不是 否则推断

由于函数参数包包含转发引用,它将根据相应参数的值类别推断int&或int。虽然也发生演绎的函数类型只能导致int的演绎。对于左值,两种演绎不一致,因此替换失败

替换失败不是一个错误,它只是从候选集中删除重载。但在你的情况下,它是唯一的候选人,所以它成为一个硬错误

所以你需要把这两个扣除分开,这意味着两包。这仍然会给我们一个int&vs.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限定符和引用类型都被删除之后,这就是Decation_t的用途。如果检查通过,则enable_If_t存在并且为void默认::类型的enable_If

给你


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

问题是有两个地方可以推断参数。始终对每个函数参数/参数对分别进行函数模板参数推导,然后进行组合

[临时扣除类型]

在某些情况下,使用一组单独的 类型P和A,在其他情况下,将有一组 对应的类型P和A。类型推导独立完成 对于每个P/A对,导出的模板参数值为 然后合并。如果无法对任何P/A对进行类型扣减, 或者,如果对任何一对进行扣减,则会导致多个可能的集合 或如果不同的对产生不同的推导值 值,或者如果任何模板参数既不是推断的,也不是 显式指定,模板参数推断失败。a型 类型参数仅从数组绑定中推导,如果它不是 否则推断

由于函数参数包包含转发引用,它将根据相应参数的值类别推断int&或int。虽然也发生演绎的函数类型只能导致int的演绎。对于左值,两种演绎不一致,因此替换失败

Fa ilure to replacement不是一个错误,它只是从候选集中删除重载。但在你的情况下,它是唯一的候选人,所以它成为一个硬错误

所以你需要把这两个扣除分开,这意味着两包。这仍然会给我们一个int&vs.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限定符和引用类型都被删除之后,这就是Decation_t的用途。如果检查通过,则enable_If_t存在并且为void默认::类型的enable_If

给你


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

如果这样编译,它看起来是一种更好的方法。因此,问题在于代理永远不会完成完美的转发,它必须始终传递一个右值int,就像std::move一样。请记住:对于完美的转发,您不能使用带有多个签名的模板类型args:这里是args与args&&,因此您的转发并不完美!大多数情况下,我相信它会做复制省略。但我还没有测试过这个理论,可以在立即调用的内联lambda中使用if constexpr执行条件std::forward来完美地实现这一点。因此,对于任何转发到值类型的引用类型,如果不是静态的,则转换为静态的。@Arne J我已经展示了详细的版本,我认为它可以正确地进行所有转发,我们可能需要向lambda添加一些额外的参数,就像显式返回类型一样。如果这样编译,它看起来是一种更好的方法。因此,问题在于代理永远不会完成完美的转发,它必须始终传递一个右值int,就像std::move一样。请记住:对于完美的转发,您不能使用带有多个签名的模板类型args:这里是args与args&&,因此您的转发并不完美!大多数情况下,我相信它会做复制省略。但我还没有测试过这个理论,可以在立即调用的内联lambda中使用if constexpr执行条件std::forward来完美地实现这一点。因此,对于任何转发到值类型的引用类型,如果不是静态的,则转换为值类型。@Arne J我已经展示了详细的版本,我认为它可以正确地进行所有转发,我们可能需要向lambda添加一些额外的参数,如显式返回类型。