C++ 使用std::invoke_result_t和泛型lambda时出现硬错误
我有一个类似容器的类,其方法类似于C++ 使用std::invoke_result_t和泛型lambda时出现硬错误,c++,c++17,sfinae,generic-lambda,c++20,C++,C++17,Sfinae,Generic Lambda,C++20,我有一个类似容器的类,其方法类似于std::apply。我想用一个const限定符重载这个方法,但是当我试图用一个通用lambda调用这个方法时,我从std::invoke\u result\t的实例化中得到一个硬错误。我使用std::invoke_result_t来推断方法的返回值,并对参数执行SFINAE检查 #include <type_traits> #include <utility> template <typename T> class Con
std::apply
。我想用一个const
限定符重载这个方法,但是当我试图用一个通用lambda调用这个方法时,我从std::invoke\u result\t
的实例化中得到一个硬错误。我使用std::invoke_result_t
来推断方法的返回值,并对参数执行SFINAE检查
#include <type_traits>
#include <utility>
template <typename T>
class Container
{
public:
template <typename F>
std::invoke_result_t<F, T &> apply(F &&f)
{
T dummyValue;
return std::forward<F>(f)(dummyValue);
}
template <typename F>
std::invoke_result_t<F, const T &> apply(F &&f) const
{
const T dummyValue;
return std::forward<F>(f)(dummyValue);
}
};
int main()
{
Container<int> c;
c.apply([](auto &&value) {
++value;
});
return 0;
}
得到了一个类似的错误:
main.cc:27:9: error: cannot assign to variable 'value' with const-qualified type 'const int &&'
++value;
^ ~~~~~
main.cc:16:41: note: in instantiation of function template specialization 'main()::(anonymous class)::operator()<const int &>' requested here
auto apply(F &&f) const -> decltype(std::declval<F>()(std::declval<const T &>()))
^
main.cc:26:7: note: while substituting deduced template arguments into function template 'apply' [with F = (lambda at main.cc:26:13)]
c.apply([](auto &&value) {
^
main.cc:26:23: note: variable 'value' declared const here
c.apply([](auto &&value) {
~~~~~~~^~~~~
main.cc:27:9:错误:无法为常量限定类型为“const int&&”的变量“value”赋值
++价值观;
^ ~~~~~
main.cc:16:41:注意:在函数模板专门化的实例化中,此处请求了“main()::(匿名类)::operator()
自动应用(F&&F)const->decltype(std::declval()(std::declval())
^
main.cc:26:7:注意:在将推导出的模板参数替换为函数模板'apply'时[使用F=(main.cc:26:13处的lambda)]
c、 应用([](自动和值){
^
main.cc:26:23:注意:变量'value'在这里声明为const
c、 应用([](自动和值){
~~~~~~~^~~~~
问题:
lambda已推导出返回类型,除非您明确指定返回类型。因此,
std::invoke_result_t
必须实例化主体以确定返回类型。此实例化不在直接上下文中,并导致硬错误
您可以通过编写以下命令来编译代码:
[](auto &&value) -> void { /* ... */ }
在这里,lambda的主体在
apply
的主体出现之前不会被实例化,并且您处于清除状态。因此重载解析在这里有点愚蠢
它并没有说“好吧,如果非const
apply
有效,我永远不会调用const-apply
,所以我不会费心考虑它”
相反,重载解析会评估每一个可能的候选者。然后,它会消除那些遭受替换失败的候选者。只有这样,它才会对候选者排序并选择一个
因此,这两种方法都将F
替换为:
template <typename F>
std::invoke_result_t<F, T &> apply(F &&f)
template <typename F>
std::invoke_result_t<F, const T &> apply(F &&f) const
执行此操作后,的两个重载都将应用
:
template <typename F>
std::invoke_result_t<F, T &> apply(F &&f)
template <typename F>
std::invoke_result_t<F, const T &> apply(F &&f) const
现在,请注意,apply const
仍然存在。如果调用apply const
,您将得到实例化lambda主体所导致的硬错误
如果希望lambda本身对SFINAE友好,则需要执行以下操作:
#define RETURNS(...) \
noexcept(noexcept(__VA_ARGS__)) \
-> decltype(__VA_ARGS__) \
{ return __VA_ARGS__; }
[](auto &&value) RETURNS(++value)
请注意,此lambda略有不同,因为它返回对值的引用。我们可以通过以下方法避免此情况:
[](auto &&value) RETURNS((void)++value)
现在lambda是SFINAE友好的,并且与原始lambda具有相同的行为,并且原始程序按照此更改进行编译
这样做的副作用是,SFINAE现在从重载解析中消除了非constapply,从而使其对SFINAE友好
有人建议使用
RETURNS
并将其重命名为=>
,但上次我检查时,它没有被接受。如果你这样做,它将被一个const
容器调用。@Yakk AdamNevraumont这只是更改了硬错误发生的位置。非const
apply不能被调用是的。@Brian,感谢您解释为什么lambda主体实例化是必要的。感谢您指出,通用lambda本身需要进行一些SFINAE检查,因为它本质上是一个模板方法的定义。
template <typename F>
std::invoke_result_t<F, T &> apply(F &&f)
template <typename F>
std::invoke_result_t<F, const T &> apply(F &&f) const
template <typename F=$lambda$>
void apply(F &&f)
template <typename F=$lambda$>
void apply(F &&f) const
#define RETURNS(...) \
noexcept(noexcept(__VA_ARGS__)) \
-> decltype(__VA_ARGS__) \
{ return __VA_ARGS__; }
[](auto &&value) RETURNS(++value)
[](auto &&value) RETURNS((void)++value)