I have a container-like class with a method that works similarly to std::apply. I would like to overload this method with a const qualifier, however when I try to invoke this method with a generic lambda I get a hard error from instantiation of std::invoke_result_t. I am using std::invoke_result_t to deduce a return value of the method as well as to perform a SFINAE check of the argument.
#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;
}
The error message while compiling with 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) {
~~~~~~~^~~~~
I'm not sure whether std::invoke_result_t is SFINAE-friendly, but I don't think that's the problem here since I've tried replacing it with a trailing return type, e.g.:
auto apply(F &&f) const -> decltype(std::declval<F>()(std::declval<const T &>()))
and got a similar error:
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) {
~~~~~~~^~~~~
Questions:
Lambdas have deduced return type, unless you specify the return type explicitly. Thus, std::invoke_result_t has to instantiate the body in order to determine the return type. This instantiation is not in the immediate context, and causes a hard error.
You can make your code compile by writing:
[](auto &&value) -> void { /* ... */ }
Here, the body of the lambda won't be instantiated until the body of apply, and you're in the clear.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With