Ell*_*ith 8 c++ templates function-pointers template-meta-programming type-deduction
我正在尝试提供一个包装器std::invoke来完成推导函数类型的工作,即使函数已重载。
(我昨天为可变参数和方法指针版本问了一个相关问题)。
当函数有一个参数时,此代码 (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));
}
Run Code Online (Sandbox Code Playgroud)
更多的输入参数显然需要减少代码膨胀,但这是一个单独的问题。
如果用户的重载仅因常量/引用而不同,如下所示:
#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);
}
Run Code Online (Sandbox Code Playgroud)
然后Invoke错误地推导出函数,用 g++ 输出:
(int &)
(const int &)(int &&)
(const int &)
和叮当++:
(int &)
(const int &)(int &&)
(int &&)
(感谢 geza 指出 clang 的输出不同)。
因此,Invoke有未定义的行为。
我怀疑元编程将是解决这个问题的方法。无论如何,是否有可能在Invoke现场正确处理类型推导?
对于每个函数 template Invoke,模板参数推导(必须成功才能进行重载解析才能考虑它)会考虑每个函数模板参数推导,看看它是否可以为所涉及的 一个函数参数 ( )Foo推导出多个模板参数(此处为两个)。只有恰好有一个匹配时,整体推导才能成功(因为否则无法推导)。(这或多或少在评论中有所说明。)funcFooS
第一个 (\xe2\x80\x9cby value\xe2\x80\x9d)Invoke永远不会存在:它可以从任何Foos 中推导出来。类似地,第二个 (\xe2\x80\x9cnon- constreference\xe2\x80\x9d) 重载接受前两个Foos。请注意,无论(for )的其他参数如何,这些都适用!Invokearg
第三个 ( const T&) 重载选择相应的Foo重载并推导出T= int;最后一个与最后一个重载(其中T&&是正常的右值引用)做了同样的事情,因此尽管它是通用引用(在这种情况下推导出Tas int&(或const int&) 并与func\xe2\x80\x99s 推导冲突),但仍拒绝左值参数。
如果 的参数arg是右值(并且像往常一样,不是\xe2\x80\x99t const),则两个合理的Invoke重载都会成功推导,并且T&&重载应该获胜(因为它将右值引用绑定到右值)。
对于评论中的案例:
\n\ntemplate <typename U>\nvoid Bar (U &&);\nint main() {\n int num;\n Invoke<void>(&Bar, num);\n}\nRun Code Online (Sandbox Code Playgroud)\n\n&Bar由于涉及函数模板,因此不会发生任何推导,因此在每种情况下T都成功推导(如int)。然后,对每种情况再次进行推导,以确定Bar要使用的专业化(如果有),分别推导U为fail、、、、和。这些情况是相同的,而且明显更好,所以这个调用是模棱两可的。int&const int&int&int&
所以Clang就在这里。(但是这里\xe2\x80\x99s没有\xe2\x80\x9c未定义的行为\xe2\x80\x9d。)
\n\n我没有\xe2\x80\x99t给你一个一般性的答案;由于某些参数类型可以接受多个值类别/常量限定对,因此在所有此类情况下正确模拟重载解析并不容易。已经有人建议以这样或那样的方式具体化重载集;您可能会考虑沿用这些思路的当前技术之一(例如每个目标函数名称的通用 lambda )。
\n| 归档时间: |
|
| 查看次数: |
158 次 |
| 最近记录: |