[英]Bad type deduction when passing overloaded function pointer and its arguments
I'm trying to provide a wrapper around std::invoke
to do the work of deducing the function type even when the function is overloaded.我正在尝试为std::invoke
提供一个包装器,以完成推导函数类型的工作,即使函数已重载。
(I asked a related question yesterday for the variadic and method pointer version). (我昨天为可变参数和方法指针版本问了一个相关问题)。
When the function has one argument this code (C++17) works as expected under normal overload conditions:当函数有一个参数时,此代码 (C++17) 在正常重载条件下按预期工作:
#include <functional>
template <typename ReturnType, typename ... Args>
using FunctionType = ReturnType (*)(Args...);
template <typename S, typename T>
auto Invoke (FunctionType<S, T> func, T arg)
{
return std::invoke(func, arg);
}
template <typename S, typename T>
auto Invoke (FunctionType<S, T&> func, T & arg)
{
return std::invoke(func, arg);
}
template <typename S, typename T>
auto Invoke (FunctionType<S, const T&> func, const T & arg)
{
return std::invoke(func, arg);
}
template <typename S, typename T>
auto Invoke (FunctionType<S, T&&> func, T && arg)
{
return std::invoke(func, std::move(arg));
}
Reducing the code bloat is obviously needed for more input arguments, but that's a separate problem.更多的输入参数显然需要减少代码膨胀,但这是一个单独的问题。
If the user has overloads differing only by const/references, like so:如果用户的重载仅因常量/引用而不同,如下所示:
#include <iostream>
void Foo (int &)
{
std::cout << "(int &)" << std::endl;
}
void Foo (const int &)
{
std::cout << "(const int &)" << std::endl;
}
void Foo (int &&)
{
std::cout << "(int &&)" << std::endl;
}
int main()
{
int num;
Foo(num);
Invoke(&Foo, num);
std::cout << std::endl;
Foo(0);
Invoke(&Foo, 0);
}
Then Invoke
deduces the function incorrectly, with g++ output:然后Invoke
错误地推断出该函数,输出为 g++:
(int &) (int &)
(const int &) (const int &)(int &&) (整数&&)
(const int &) (const int &)
And clang++:和叮当++:
(int &) (int &)
(const int &) (const int &)(int &&) (整数&&)
(int &&) (整数&&)
(Thanks to geza for pointing out that clang's outputs were different). (感谢 geza 指出 clang 的输出不同)。
So Invoke
has undefined behaviour.因此Invoke
具有未定义的行为。
I suspect that metaprogramming would be the way to approach this problem.我怀疑元编程将是解决这个问题的方法。 Regardless, is it possible to handle the type deduction correctly at the Invoke
site?无论如何,是否可以在Invoke
站点正确处理类型推导?
For each function template Invoke
, the template argument deduction (that must succeed for overload resolution to consider it) considers each Foo
to see whether it can deduce however many template parameters (here, two) for the one function parameter ( func
) involved.对于每个函数模板Invoke
,模板参数推导(必须成功才能考虑重载决议)考虑每个Foo
以查看它是否可以为所涉及的一个函数参数( func
)推导出多个模板参数(此处为两个)。 The overall deduction can succeed only if exactly one Foo
matches (because otherwise there is no way to deduce S
).只有当恰好有一个Foo
匹配时,整体推导才能成功(因为否则无法推导S
)。 (This was more or less stated in the comments.) (这或多或少在评论中有所说明。)
The first (“by value”) Invoke
never survives: it can deduce from any of the Foo
s.第一个(“按值”) Invoke
永远不会存活:它可以从任何Foo
推导出来。 Similarly, the second (“non- const
reference”) overload accepts the first two Foo
s.类似地,第二(“非const
参考”)过载接受前两个Foo
秒。 Note that these apply regardless of the other argument to Invoke
(for arg
)!请注意,无论Invoke
的其他参数如何(对于arg
),这些都适用!
The third ( const T&
) overload selects the corresponding Foo
overload and deduces T
= int
;第三个( const T&
)重载选择相应的Foo
重载并推导出T
= int
; the last does the same thing with the last overload (where T&&
is a normal rvalue reference), and therefore rejects lvalue arguments despite its universal reference (which deduces T
as int&
(or const int&
) in that case and conflicts with func
's deduction).最后一个与最后一个重载(其中T&&
是正常的右值引用)做同样的事情,因此拒绝左值参数,尽管它的通用引用(在这种情况下将T
推导出为int&
(或const int&
)并与func
的推导冲突)。
If the argument for arg
is an rvalue (and, as usual, isn't const), both plausible Invoke
overloads succeed at deduction, and the T&&
overload should win (because it binds an rvalue reference to an rvalue ).如果arg
是一个右值(并且像往常一样,不是 const),那么两个合理的Invoke
重载在推导时都会成功,并且T&&
重载应该获胜(因为它将右值引用绑定到一个rvalue )。
For the case from the comments:对于评论中的案例:
template <typename U>
void Bar (U &&);
int main() {
int num;
Invoke<void>(&Bar, num);
}
No deduction takes place from &Bar
since a function template is involved, so T
is successfully deduced (as int
) in every case.由于涉及函数模板,因此不会从&Bar
推导,因此在每种情况下都成功推导了T
(作为int
)。 Then, deduction happens again for each case to identify the Bar
specialization (if any) to use, deducing U
as fail , int&
, const int&
, and int&
respectively.然后,对每种情况再次进行推导,以确定要使用的Bar
(如果有),分别推导出U
为fail 、 int&
、 const int&
和int&
。 The int&
cases are identical and plainly better, so the call is ambiguous. int&
情况是相同的,而且明显更好,所以调用是模棱两可的。
So Clang is right here.所以Clang就在这里。 (But there's no “undefined behavior” here.) (但这里没有“未定义的行为”。)
I don't have a general answer for you;我没有给你一个通用的答案; since certain parameter types can accept multiple value-category/const-qualification pairs, it's not going to be easy to emulate overload resolution correctly in all such cases.由于某些参数类型可以接受多个值类别/常量限定对,因此在所有这些情况下都不容易正确模拟重载解析。 There have been proposals to reify overload sets in one way or another;已经有人提议以某种方式具体化重载集; you might consider one of the current techniques along those lines (like a generic lambda per target function name).您可能会考虑这些方面的当前技术之一(例如每个目标函数名称的通用 lambda )。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.