Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hard error when using std::invoke_result_t with a generic lambda

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:

  1. Why does this happen? More accurately, why is lambda's body instantiated during, what seems to be, an overload resolution?
  2. How do I work around it?
like image 856
r3mus n0x Avatar asked Dec 24 '22 02:12

r3mus n0x


1 Answers

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.

like image 145
Brian Bi Avatar answered Jan 11 '23 23:01

Brian Bi