4

I'm trying to write a fairly simple method that returns a future. A lambda sets the future. This is a minimal example. In reality the lambda might be invoked in a different thread, etc.

#include <future>

std::future<std::error_code> do_something() {
  std::promise<std::error_code> p;
  auto fut = p.get_future();
  auto lambda = [p = std::move(p)] {
    std::error_code error;
    p.set_value(error);
  };
  lambda();
  return std::move(fut);
}

int main() { return do_something().get().value(); }

For some reason I get a type error. VSCode intellisense says:

no instance of overloaded function "std::promise<_Ty>::set_value [with _Ty=std::error_code]" matches the argument list and object (the object has type qualifiers that prevent a match) -- argument types are: (std::remove_reference_t<std::error_code &>) -- object type is: const std::remove_reference_t<std::promise<std::error_code> &>

And MSVC compiler says:

error C2663: 'std::promise<std::error_code>::set_value': 2 overloads have no legal conversion for 'this' pointer

I really don't understand the VS Code error. Is it saying that it thinks error is a const promise<error_code>? How do I correctly call set_value on a promise which was moved inside a lambda's capture?

MHebes
  • 2,290
  • 1
  • 16
  • 29
  • Just add `mutable` keyword to lambda, `[...](...) mutable { ... }`. Mutable keyword allows to modify captured values. – Arty Nov 18 '21 at 16:07
  • If `mutable` keywoard is not supported, I don't remember if C++11 supports it, then do following `[... p = std::make_shared(std::move(p)) ...](...) { ... }` - in other words wrap your value into [std::shared_ptr](https://en.cppreference.com/w/cpp/memory/shared_ptr), because constant shared pointer allows to modify its underlying object. Also you can use `std::unique_ptr` instead of shared. If you wrap into shared pointer then don't forget inside lambda body to use it as a pointer, i.e. dereference it through `->` like `p->SomeMethod();`. – Arty Nov 18 '21 at 16:14
  • Thanks a bunch @Arty. If you make an answer I'll mark it! – MHebes Nov 18 '21 at 16:15
  • Great! Wait 5 minutes and I'll create an answer. – Arty Nov 18 '21 at 16:16
  • 1
    I created an [Answer](https://stackoverflow.com/a/70023348/941531), you may Accept and/or UpVote it. Thanks for interesting Question! – Arty Nov 18 '21 at 16:35
  • 1
    @Arty c++11 had mutable lambdas, and OP is move-capturing (c++14) – sehe Nov 18 '21 at 17:25

1 Answers1

5

By default lambda stores all its captured values (non-references) as const values, you can't modify them. But lambda supports keyword mutable, you can add it like this:

[/*...*/](/*...*/) mutable { /*...*/ }

This will allow inside body of a lambda to modify all its values.

If for some reason you can't use mutable, then you can use other work-around:

[/*...*/, p = std::make_shared<ClassName>(std::move(p)), /* ... */](/*...*/) {/*...*/}

In other words wrap your moved value into std::shared_ptr, you can also use std::unique_ptr if you like.

Wrapping into shared pointer solves the problem, because shared pointer (unique also) allows to modify its underlying object value even if pointer itself is const.

Don't forget inside the body of a lambda to dereference p as a pointer, in other words if you used p.SomeMethod(), now you have to use p->SomeMethod() (with -> operator).

Arty
  • 14,883
  • 6
  • 36
  • 69