我正在尝试生成一个智能代理功能,以便按照给定的方式转发参数。
代码如下:
#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
。
我在这里做错什么了吗?
答案 0 :(得分:3)
问题在于有两个地方可以推论ARGS
。始终对每个功能参数/参数对分别进行功能模板参数推导,然后组合。
[温度扣除类型]
2在某些情况下,推导是使用一组 类型
P
和A
,在其他情况下,将会有一组 相应的类型P
和A
。类型推导是独立完成的 对于每个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);
}