将 std::invoke_result_t 与通用 lambda 一起使用时出现硬错误
Hard error when using std::invoke_result_t with a generic lambda
我有一个类似容器的 class,其工作方式与 std::apply
类似。我想用 const
限定符重载此方法,但是当我尝试使用通用 lambda 调用此方法时,我从 std::invoke_result_t
的实例化中遇到了硬错误。我正在使用 std::invoke_result_t
来推断方法的 return 值以及对参数执行 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;
}
使用 Clang 6.0 编译时的错误信息:
main.cc:27:9: error: cannot assign to variable 'value' with const-qualified type 'const int &&'
++value;
^ ~~~~~
type_traits:2428:7: note: in instantiation of function template specialization 'main()::(anonymous class)::operator()<const int &>' requested here
std::declval<_Fn>()(std::declval<_Args>()...)
^
type_traits:2439:24: note: while substituting deduced template arguments into function template '_S_test' [with _Fn = (lambda at main.cc:26:13), _Args = (no value)]
typedef decltype(_S_test<_Functor, _ArgTypes...>(0)) type;
^
type_traits:2445:14: note: in instantiation of template class 'std::__result_of_impl<false, false, (lambda at main.cc:26:13), const int &>' requested here
: public __result_of_impl<
^
type_traits:2831:14: note: in instantiation of template class 'std::__invoke_result<(lambda at main.cc:26:13), const int &>' requested here
: public __invoke_result<_Functor, _ArgTypes...>
^
type_traits:2836:5: note: in instantiation of template class 'std::invoke_result<(lambda at main.cc:26:13), const int &>' requested here
using invoke_result_t = typename invoke_result<_Fn, _Args...>::type;
^
main.cc:16:10: note: in instantiation of template type alias 'invoke_result_t' requested here
std::invoke_result_t<F, const T &> apply(F &&f) const
^
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) {
~~~~~~~^~~~~
我不确定 std::invoke_result_t
是否对 SFINAE 友好,但我不认为这是这里的问题,因为我已经尝试用尾随 return 类型替换它,例如:
auto apply(F &&f) const -> decltype(std::declval<F>()(std::declval<const T &>()))
并得到了类似的错误:
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) {
~~~~~~~^~~~~
问题:
- 为什么会这样?更准确地说,为什么 lambda 的主体在似乎是重载解析期间实例化?
- 我该如何解决?
Lambda 已推导出 return 类型,除非您明确指定 return 类型。因此,std::invoke_result_t
必须实例化主体以确定 return 类型。此实例化不在直接上下文中,并导致硬错误。
您可以通过以下方式编译代码:
[](auto &&value) -> void { /* ... */ }
在这里,lambda 的主体在 apply
的主体之前不会被实例化,你是清楚的。
所以重载决议在这里有点愚蠢。
它没有说 "well, if non-const
apply
works, I'll never call const apply
, so I won't bother considering it"。
相反,重载决策会评估每个可能的候选者。然后它会消除那些遭受替代失败的人。只有这样它才会对候选人进行排序并选择一个。
所以这两个 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
我移除了他们的尸体。
现在,当您将 F
的 lambda 类型传递给这些时会发生什么?
好吧,lambdas 相当于 auto
return 类型。为了在传递某些东西时找出实际的 return 类型,编译器必须检查 lambda 的主体。
并且 SFINAE 在检查函数体(或 lambda)时不起作用。这是为了让编译器的工作更轻松(因为 SFINAE 对编译器来说非常困难,让他们必须编译任意代码并且 运行 变成任意错误然后将其回滚是一个巨大的障碍)。
我们可以这样避免实例化 lambda 的主体:
[](auto &&value) -> void { /* ... */ }
完成后,apply
:
的两个重载
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
可以计算 return 值(它只是 void
),我们得到:
template <typename F=$lambda$>
void apply(F &&f)
template <typename F=$lambda$>
void 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 略有不同,因为它 return 是对值的引用。我们可以通过以下方式避免这种情况:
[](auto &&value) RETURNS((void)++value)
现在 lambda 对 SFINAE 都友好 and 与原始 lambda 具有相同的行为 and 您的原始程序按原样编译有了这个改变。
这样做的副作用是非const
应用现在已从 SFINAE 的重载解析中消除。这反过来又使它对 SFINAE 友好。
曾有人提议采用 RETURNS
并将其重命名为 =>
,但最后我检查了 c++20 未接受它。
我有一个类似容器的 class,其工作方式与 std::apply
类似。我想用 const
限定符重载此方法,但是当我尝试使用通用 lambda 调用此方法时,我从 std::invoke_result_t
的实例化中遇到了硬错误。我正在使用 std::invoke_result_t
来推断方法的 return 值以及对参数执行 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;
}
使用 Clang 6.0 编译时的错误信息:
main.cc:27:9: error: cannot assign to variable 'value' with const-qualified type 'const int &&'
++value;
^ ~~~~~
type_traits:2428:7: note: in instantiation of function template specialization 'main()::(anonymous class)::operator()<const int &>' requested here
std::declval<_Fn>()(std::declval<_Args>()...)
^
type_traits:2439:24: note: while substituting deduced template arguments into function template '_S_test' [with _Fn = (lambda at main.cc:26:13), _Args = (no value)]
typedef decltype(_S_test<_Functor, _ArgTypes...>(0)) type;
^
type_traits:2445:14: note: in instantiation of template class 'std::__result_of_impl<false, false, (lambda at main.cc:26:13), const int &>' requested here
: public __result_of_impl<
^
type_traits:2831:14: note: in instantiation of template class 'std::__invoke_result<(lambda at main.cc:26:13), const int &>' requested here
: public __invoke_result<_Functor, _ArgTypes...>
^
type_traits:2836:5: note: in instantiation of template class 'std::invoke_result<(lambda at main.cc:26:13), const int &>' requested here
using invoke_result_t = typename invoke_result<_Fn, _Args...>::type;
^
main.cc:16:10: note: in instantiation of template type alias 'invoke_result_t' requested here
std::invoke_result_t<F, const T &> apply(F &&f) const
^
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) {
~~~~~~~^~~~~
我不确定 std::invoke_result_t
是否对 SFINAE 友好,但我不认为这是这里的问题,因为我已经尝试用尾随 return 类型替换它,例如:
auto apply(F &&f) const -> decltype(std::declval<F>()(std::declval<const T &>()))
并得到了类似的错误:
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) {
~~~~~~~^~~~~
问题:
- 为什么会这样?更准确地说,为什么 lambda 的主体在似乎是重载解析期间实例化?
- 我该如何解决?
Lambda 已推导出 return 类型,除非您明确指定 return 类型。因此,std::invoke_result_t
必须实例化主体以确定 return 类型。此实例化不在直接上下文中,并导致硬错误。
您可以通过以下方式编译代码:
[](auto &&value) -> void { /* ... */ }
在这里,lambda 的主体在 apply
的主体之前不会被实例化,你是清楚的。
所以重载决议在这里有点愚蠢。
它没有说 "well, if non-const
apply
works, I'll never call const apply
, so I won't bother considering it"。
相反,重载决策会评估每个可能的候选者。然后它会消除那些遭受替代失败的人。只有这样它才会对候选人进行排序并选择一个。
所以这两个 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
我移除了他们的尸体。
现在,当您将 F
的 lambda 类型传递给这些时会发生什么?
好吧,lambdas 相当于 auto
return 类型。为了在传递某些东西时找出实际的 return 类型,编译器必须检查 lambda 的主体。
并且 SFINAE 在检查函数体(或 lambda)时不起作用。这是为了让编译器的工作更轻松(因为 SFINAE 对编译器来说非常困难,让他们必须编译任意代码并且 运行 变成任意错误然后将其回滚是一个巨大的障碍)。
我们可以这样避免实例化 lambda 的主体:
[](auto &&value) -> void { /* ... */ }
完成后,apply
:
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
可以计算 return 值(它只是 void
),我们得到:
template <typename F=$lambda$>
void apply(F &&f)
template <typename F=$lambda$>
void 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 略有不同,因为它 return 是对值的引用。我们可以通过以下方式避免这种情况:
[](auto &&value) RETURNS((void)++value)
现在 lambda 对 SFINAE 都友好 and 与原始 lambda 具有相同的行为 and 您的原始程序按原样编译有了这个改变。
这样做的副作用是非const
应用现在已从 SFINAE 的重载解析中消除。这反过来又使它对 SFINAE 友好。
曾有人提议采用 RETURNS
并将其重命名为 =>
,但最后我检查了 c++20 未接受它。