我有一个类似容器的类,其方法与std::apply
类似。我想用const
限定符重载此方法,但是当我尝试使用通用lambda调用此方法时,由于实例化std::invoke_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;
}
使用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友好,但是我不认为这是问题所在,因为我尝试用尾随返回类型(例如:
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) {
~~~~~~~^~~~~
问题:
答案 0 :(得分:4)
Lambda推断出返回类型,除非您明确指定返回类型。因此,std::invoke_result_t
必须实例化主体才能确定返回类型。此实例不在即时上下文中,并且会导致硬错误。
您可以通过编写以下代码来编译代码:
[](auto &&value) -> void { /* ... */ }
在这里,lambda的主体直到apply
的主体才被实例化,并且您无事。
答案 1 :(得分:4)
所以过载解决方案在这里有点愚蠢。
它没有说“好吧,如果非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
我移走了他们的尸体。
现在,当您将F
的lambda类型传递给它们时会发生什么?
好吧,lambda与返回类型auto
等效。为了找出传递某些内容时的实际返回类型,编译器必须检查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
可以评估返回值(它只是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略有不同,因为它返回对该值的引用。我们可以通过以下方法避免这种情况:
[](auto &&value) RETURNS((void)++value)
现在,lambda既对SFINAE友好,又具有与原始lambda 相同的行为,并且您的原始程序通过此更改按原样编译。
这样做的副作用是SFINAE现在从重载解析中消除了非const
适用。这又使其对SFINAE友好。
有人建议取RETURNS
并将其重命名为=>
,但最后我检查了c++20的接受度。