implementation details of future lead to type errors using when_all with inference

Issue #288 resolved
Dan Bonachea created an issue

Consider the following simple code: (a distilled version of a problem in real, useful code)

#include <upcxx/upcxx.hpp>

upcxx::future<int> foo(upcxx::global_ptr<int> gp) {
  upcxx::future<int> f = upcxx::rget(gp);
  return f.then([=](int result)  {
    if (result < 42) return upcxx::make_future(result);
    else return upcxx::rget(gp+1);
  });
}

By my reading of the UPC++ and C++11 specs, this code should compile.

In particular the UPC++ spec 5.8.1-10 dictates the return type of the expression make_future(int) should be future<int>, and by 8.2.2-4 and 7.2.2-4 the return type of the expression rget(global_ptr<int>) is also required to be future<int>.

However this code currently fails to typecheck - it gets a type inference error and complains about a type mismatch between the two return statements in the lambda - example output from 2019.9.0/gcc-9.2.0 on dirac:

future-inference.cpp: In lambda function:
future-inference.cpp:7:33: error: inconsistent types 'upcxx::future1<upcxx::detail::future_kind_result, int>' and 'upcxx::future1<upcxx::detail::future_kind_shref<upcxx::detail::future_header_ops_result>, int>' deduced for lambda return type
    7 |     else return upcxx::rget(gp+1);
      |                                 ^
In file included from /usr/local/pkg/upcxx-dirac/gcc-9.2.0/stable-2019.9.0/upcxx.debug.gasnet_seq.ibv/include/upcxx/future.hpp:5,
                 from /usr/local/pkg/upcxx-dirac/gcc-9.2.0/stable-2019.9.0/upcxx.debug.gasnet_seq.ibv/include/upcxx/backend.hpp:5,
                 from /usr/local/pkg/upcxx-dirac/gcc-9.2.0/stable-2019.9.0/upcxx.debug.gasnet_seq.ibv/include/upcxx/allocate.hpp:8,
                 from /usr/local/pkg/upcxx-dirac/gcc-9.2.0/stable-2019.9.0/upcxx.debug.gasnet_seq.ibv/include/upcxx/upcxx.hpp:12,
                 from future-inference.cpp:1:
/usr/local/pkg/upcxx-dirac/gcc-9.2.0/stable-2019.9.0/upcxx.debug.gasnet_seq.ibv/include/upcxx/future/future1.hpp: In instantiation of 'upcxx::future1<Kind, T>::future1(upcxx::future1<Kind1, T ...>&&) [with Kind1 = upcxx::detail::future_kind_shref<upcxx::detail::future_header_ops_result>; Kind = upcxx::detail::future_kind_result; T = {int}]':
future-inference.cpp:7:33:   required from here
/usr/local/pkg/upcxx-dirac/gcc-9.2.0/stable-2019.9.0/upcxx.debug.gasnet_seq.ibv/include/upcxx/future/future1.hpp:102:69: error: no matching function for call to 'upcxx::detail::future_impl_result<int>::future_impl_result(std::remove_reference<upcxx::detail::future_impl_shref<upcxx::detail::future_header_ops_result, int>&>::type)'
  102 |     future1(future1<Kind1,T...> &&that): impl_(std::move(that.impl_)) {}
      |                                                                     ^
In file included from /usr/local/pkg/upcxx-dirac/gcc-9.2.0/stable-2019.9.0/upcxx.debug.gasnet_seq.ibv/include/upcxx/future/make_future.hpp:4,
                 from /usr/local/pkg/upcxx-dirac/gcc-9.2.0/stable-2019.9.0/upcxx.debug.gasnet_seq.ibv/include/upcxx/future/apply.hpp:5,
                 from /usr/local/pkg/upcxx-dirac/gcc-9.2.0/stable-2019.9.0/upcxx.debug.gasnet_seq.ibv/include/upcxx/future.hpp:7,
                 from /usr/local/pkg/upcxx-dirac/gcc-9.2.0/stable-2019.9.0/upcxx.debug.gasnet_seq.ibv/include/upcxx/backend.hpp:5,
                 from /usr/local/pkg/upcxx-dirac/gcc-9.2.0/stable-2019.9.0/upcxx.debug.gasnet_seq.ibv/include/upcxx/allocate.hpp:8,
                 from /usr/local/pkg/upcxx-dirac/gcc-9.2.0/stable-2019.9.0/upcxx.debug.gasnet_seq.ibv/include/upcxx/upcxx.hpp:12,
                 from future-inference.cpp:1:
/usr/local/pkg/upcxx-dirac/gcc-9.2.0/stable-2019.9.0/upcxx.debug.gasnet_seq.ibv/include/upcxx/future/impl_result.hpp:27:7: note: candidate: 'upcxx::detail::future_impl_result<T>::future_impl_result(T ...) [with T = {int}]'
   27 |       future_impl_result(T ...values):
      |       ^~~~~~~~~~~~~~~~~~
/usr/local/pkg/upcxx-dirac/gcc-9.2.0/stable-2019.9.0/upcxx.debug.gasnet_seq.ibv/include/upcxx/future/impl_result.hpp:27:28: note:   no known conversion for argument 1 from 'std::remove_reference<upcxx::detail::future_impl_shref<upcxx::detail::future_header_ops_result, int>&>::type' {aka 'upcxx::detail::future_impl_shref<upcxx::detail::future_header_ops_result, int>'} to 'int'
   27 |       future_impl_result(T ...values):
      |                          ~~^~~~~~~~~
/usr/local/pkg/upcxx-dirac/gcc-9.2.0/stable-2019.9.0/upcxx.debug.gasnet_seq.ibv/include/upcxx/future/impl_result.hpp:23:12: note: candidate: 'constexpr upcxx::detail::future_impl_result<int>::future_impl_result(const upcxx::detail::future_impl_result<int>&)'
   23 |     struct future_impl_result {
      |            ^~~~~~~~~~~~~~~~~~
/usr/local/pkg/upcxx-dirac/gcc-9.2.0/stable-2019.9.0/upcxx.debug.gasnet_seq.ibv/include/upcxx/future/impl_result.hpp:23:12: note:   no known conversion for argument 1 from 'std::remove_reference<upcxx::detail::future_impl_shref<upcxx::detail::future_header_ops_result, int>&>::type' {aka 'upcxx::detail::future_impl_shref<upcxx::detail::future_header_ops_result, int>'} to 'const upcxx::detail::future_impl_result<int>&'
/usr/local/pkg/upcxx-dirac/gcc-9.2.0/stable-2019.9.0/upcxx.debug.gasnet_seq.ibv/include/upcxx/future/impl_result.hpp:23:12: note: candidate: 'constexpr upcxx::detail::future_impl_result<int>::future_impl_result(upcxx::detail::future_impl_result<int>&&)'
/usr/local/pkg/upcxx-dirac/gcc-9.2.0/stable-2019.9.0/upcxx.debug.gasnet_seq.ibv/include/upcxx/future/impl_result.hpp:23:12: note:   no known conversion for argument 1 from 'std::remove_reference<upcxx::detail::future_impl_shref<upcxx::detail::future_header_ops_result, int>&>::type' {aka 'upcxx::detail::future_impl_shref<upcxx::detail::future_header_ops_result, int>'} to 'upcxx::detail::future_impl_result<int>&&'

This failure persists in develop and at all supported language levels -std=c++{11,14,17,2a}.

The problem here appears to be the runtime's unspecified games using more than one underlying C++ type to represent the normative type future<int>, depending on the context in which the future is created. This has mostly been cleverly hidden, but this is one example of where the transparency of this implementation technique has failed and the result is an obscure type check failure of code that should compile according to spec.

In this particular case there happens to be an available workaround, which is to explicitly declare a trailing result type for the lambda as -> future<int>, which forces the necessary hidden type conversions to happen inside the implementation. However an average user who doesn't understand how the runtime internals work is unlikely to guess or understand why that would fix this type check error.

@john bachan: Is there a way we can improve this situation?

Comments (12)

  1. john bachan

    Unfortunately, I don't think we can support this inference without sacrificing performance. The different flavors of future types permits optimizations that I think are critical. For instance make_future<some_trivial_type>() returns a future-like type that is also fairly trivial (no heap allocated thunk). This has some cool properties:

    1. This future has no reference counting overhead.

    2. Calling .ready() on such a future is statically known true (enables constant folding optimizations).

    3. Calling .then() on such a future just execute the callback now, eliding the heap thunk and the dispatch is statically know thus inlineable.

    4. Returning such a future from a .then() callback statically elides overheads with tracking the dependency usually present when the outer future proxies for the inner one.

    These optimizations are a huge win within the runtime, for instance in rpc(). Lots of dependency tracking is elided for rpc lambdas that return such trivial future types yet the implementation is just idiomatic applications of .then() (great for my maintainability).

    It is arguable that user code is typically less generic than runtime code, and so users may benefit less than I do. But since the number of cases where these type games surprise users is rare, is it worth sacrificing the performance? Tough call.

    It's worth noting that usually when a user is giving us a trivial future, they aren't really giving us a future, but rather a naked value, for ex: fut.then([=]() { return 42; });. Even if we reimplement make_future to return the non-trivial future<T>, that wouldn't inhibit then from seeing that the callback's return of 42 is a statically trivial future.

    I'm mostly in favor of keeping the implementation as-is to promote maximum performance and changing the spec to return non-conrete type-concept members.

  2. Dan Bonachea reporter

    This was discussed in the 2020-02-12 meeting and deferred to next release milestone.

    One idea that was proposed (but not yet investigated) would be to provide both concrete future<T> and abstract future_opt<T> types, where the former was a guaranteed concrete/unique type and the latter was permitted to be a context-dependent optimized type like the current implementation. This way the implementation could continue to leverage the optimization, while user code could opt-in.

    However it's worth noting that in order to solve the problem in the original post, the make_future we expose to users would need to change to return the concrete future<T>, and possibly provide a make_future_opt() that returned the "slim" future type. However explaining to users the right circumstances in which to select the latter would not be straightforward.

    Also of relevance to this discussion is spec issue #107, which argues for RMA entry points that are permitted to return readied futures. This could benefit from the use of "slim" futures, at least for value rget - RMA's returning empty futures could presumably all return the same readied "full" future.

  3. john bachan

    PR 178 has made some progress here. All known future-returning API calls now return future<T> which is an alias for future1</*default-kind*/,T>, as opposed to future1</*non-default-kind*/,T>. The only exception to this is when_all, which still returns future1<?,T>. So the casting issue driving this whole thing should have a reduced incident rate. The runtime still leverages the fancy types, but uses API analogs like detail::make_fast_future to get them.

  4. Dan Bonachea reporter

    In the interests of documenting status, here's two concrete examples of the compliance gap remaining after Pull request #178 leading to a type error:

    future<int> f;
    future<> f2;
    future<int> foo() {
      return f.ready() ? f : when_all(f2, f);
    }
    void foo2() {
      rpc(0, []() {
            if (f.ready()) 
              return f;
            else 
              return when_all(f2, f);
          }).wait();
    }
    

    Errors:

    future-ref2.cpp: In function 'upcxx::future<int> foo()':
    future-ref2.cpp:18:20: error: operands to '?:' have different types 'upcxx::future<int>' {aka 'upcxx::future1<upcxx::detail::future_kind_shref<upcxx::detail::future_header_ops_general>, int>'} and 'upcxx::detail::when_all_return_t<upcxx::future1<upcxx::detail::future_kind_shref<upcxx::detail::future_header_ops_general, false> >&, upcxx::future1<upcxx::detail::future_kind_shref<upcxx::detail::future_header_ops_general, false>, int>&>' {aka 'upcxx::future1<upcxx::detail::future_kind_when_all<upcxx::future1<upcxx::detail::future_kind_shref<upcxx::detail::future_header_ops_general, false> >, upcxx::future1<upcxx::detail::future_kind_shref<upcxx::detail::future_header_ops_general, false>, int> >, int>'}
       18 |   return f.ready() ? f : when_all(f2, f);
          |          ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~
    future-ref2.cpp:18:20: note:   and each type can be converted to the other
    future-ref2.cpp: In lambda function:
    future-ref2.cpp:25:26: error: inconsistent types 'upcxx::future1<upcxx::detail::future_kind_shref<upcxx::detail::future_header_ops_general>, int>' and 'upcxx::future1<upcxx::detail::future_kind_when_all<upcxx::future1<upcxx::detail::future_kind_shref<upcxx::detail::future_header_ops_general, false> >, upcxx::future1<upcxx::detail::future_kind_shref<upcxx::detail::future_header_ops_general, false>, int> >, int>' deduced for lambda return type
       25 |           return when_all(f2, f);
          |                  ~~~~~~~~^~~~~~~
    

    Until we see a user-driven example running afowl of this remaining defect, I'm willing to believe John's claim that such errors should arise less commonly in practice, so downgrading priority to minor.

  5. Rob Egan

    Just to comment on this (and document my work around). I think that I ran into this issue when trying to build against upcxx <= 2020.3.2, but not the 2020.8 snapshot

    https://bitbucket.org/berkeleylab/upcxx-utils/commits/75aa4c1b78381b827595e8328eafeca60005ba55

    The problem was in a function returning the results of an auto type, not an explicit future<> type.

    auto fut = make_future();

    vs

    future<> fut = make_future();

    and then assigning that to the output of an rpc

    fut = rpc(…)

    And this was the compiler message that mentionsreduce_prefix.hpp:634 which is the line that assigned the future to the return value of the rpc.

    In file included from /usr/common/ftg/upcxx/2020.3.2/craype-2.6.2/knl/gnu/PrgEnv-gnu-6.0.5-8.3.0/upcxx.debug.gasnet_seq.aries/include/upcxx/future.hpp:5,
                     from /usr/common/ftg/upcxx/2020.3.2/craype-2.6.2/knl/gnu/PrgEnv-gnu-6.0.5-8.3.0/upcxx.debug.gasnet_seq.aries/include/upcxx/backend.hpp:5,
                     from /usr/common/ftg/upcxx/2020.3.2/craype-2.6.2/knl/gnu/PrgEnv-gnu-6.0.5-8.3.0/upcxx.debug.gasnet_seq.aries/include/upcxx/allocate.hpp:8,
                     from /usr/common/ftg/upcxx/2020.3.2/craype-2.6.2/knl/gnu/PrgEnv-gnu-6.0.5-8.3.0/upcxx.debug.gasnet_seq.aries/include/upcxx/upcxx.hpp:17,
                     from /global/homes/r/regan/workspace-shared/upcxx-utils/include/upcxx_utils/ofstream.hpp:17,
                     from /global/homes/r/regan/workspace-shared/upcxx-utils/src/ofstream.cpp:1:
    /usr/common/ftg/upcxx/2020.3.2/craype-2.6.2/knl/gnu/PrgEnv-gnu-6.0.5-8.3.0/upcxx.debug.gasnet_seq.aries/include/upcxx/future/future1.hpp: In instantiation of 'upcxx:
    :future1<Kind, T>& upcxx::future1<Kind, T>::operator=(upcxx::future1<Kind1, T ...>&&) [with Kind1 = upcxx::detail::future_kind_shref<upcxx::detail::future_header_ops
    _promise>; Kind = upcxx::detail::future_kind_result; T = {}]':
    /global/homes/r/regan/workspace-shared/upcxx-utils/include/upcxx_utils/reduce_prefix.hpp:634:46:   required from 'upcxx::future<> upcxx_utils::reduce_prefix_binary_t
    ree_down(upcxx_utils::ShDistData<T, BinaryOp>) [with T = long unsigned int; BinaryOp = upcxx::detail::op_wrap<upcxx::detail::opfn_add, true>; upcxx::future<> = upcxx
    ::future1<upcxx::detail::future_kind_shref<upcxx::detail::future_header_ops_general> >; upcxx_utils::ShDistData<T, BinaryOp> = std::shared_ptr<upcxx::dist_object<std
    ::tuple<const long unsigned int*, long unsigned int*, long unsigned int, const upcxx::detail::op_wrap<upcxx::detail::opfn_add, true>&, std::shared_ptr<upcxx_utils::b
    inary_tree_steps>, std::shared_ptr<std::vector<long unsigned int, std::allocator<long unsigned int> > >, upcxx_utils::in_order_binary_tree_node, bool> > >]'
    /global/homes/r/regan/workspace-shared/upcxx-utils/include/upcxx_utils/reduce_prefix.hpp:723:54:   required from 'upcxx::future<> upcxx_utils::reduce_prefix_binary_t
    ree(const T*, T*, size_t, const BinaryOp&, const upcxx::team&, bool, upcxx::intrank_t, size_t) [with T = long unsigned int; BinaryOp = upcxx::detail::op_wrap<upcxx::
    detail::opfn_add, true>; upcxx::future<> = upcxx::future1<upcxx::detail::future_kind_shref<upcxx::detail::future_header_ops_general> >; size_t = long unsigned int; u
    pcxx::intrank_t = int]'
    /global/homes/r/regan/workspace-shared/upcxx-utils/include/upcxx_utils/reduce_prefix.hpp:880:45:   required from 'upcxx::future<> upcxx_utils::reduce_prefix_auto_cho
    ice(const T*, T*, size_t, const BinaryOp&, const upcxx::team&, bool, int) [with T = long unsigned int; BinaryOp = upcxx::detail::op_wrap<upcxx::detail::opfn_add, tru
    e>; upcxx::future<> = upcxx::future1<upcxx::detail::future_kind_shref<upcxx::detail::future_header_ops_general> >; size_t = long unsigned int]'
    /global/homes/r/regan/workspace-shared/upcxx-utils/include/upcxx_utils/reduce_prefix.hpp:945:5:   required from 'upcxx::future<T> upcxx_utils::reduce_prefix_one_auto_choice(T, const BinaryOp&, const upcxx::team&, bool) [with T = long unsigned int; BinaryOp = upcxx::detail::op_wrap<upcxx::detail::opfn_add, true>; upcxx::future<T> = upcxx::future1<upcxx::detail::future_kind_shref<upcxx::detail::future_header_ops_general>, long unsigned int>]'
    /global/homes/r/regan/workspace-shared/upcxx-utils/include/upcxx_utils/reduce_prefix.hpp:950:45:   required from 'upcxx::future<T> upcxx_utils::reduce_prefix(T, const BinaryOp&, const upcxx::team&, bool) [with T = long unsigned int; BinaryOp = upcxx::detail::op_wrap<upcxx::detail::opfn_add, true>; upcxx::future<T> = upcxx::future1<upcxx::detail::future_kind_shref<upcxx::detail::future_header_ops_general>, long unsigned int>]'
    /global/homes/r/regan/workspace-shared/upcxx-utils/src/ofstream.cpp:515:92:   required from here
    /usr/common/ftg/upcxx/2020.3.2/craype-2.6.2/knl/gnu/PrgEnv-gnu-6.0.5-8.3.0/upcxx.debug.gasnet_seq.aries/include/upcxx/future/future1.hpp:156:19: error: no match for 'operator=' (operand types are 'upcxx::future1<upcxx::detail::future_kind_result>::impl_type' {aka 'upcxx::detail::future_impl_result<>'} and 'std::remove_reference<upcxx::detail::future_impl_shref<upcxx::detail::future_header_ops_promise>&>::type' {aka 'upcxx::detail::future_impl_shref<upcxx::detail::future_header_ops_promise>'})
           this->impl_ = std::move(that.impl_);
           ~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~
    In file included from /usr/common/ftg/upcxx/2020.3.2/craype-2.6.2/knl/gnu/PrgEnv-gnu-6.0.5-8.3.0/upcxx.debug.gasnet_seq.aries/include/upcxx/future/make_future.hpp:4,
                     from /usr/common/ftg/upcxx/2020.3.2/craype-2.6.2/knl/gnu/PrgEnv-gnu-6.0.5-8.3.0/upcxx.debug.gasnet_seq.aries/include/upcxx/future/apply.hpp:5,
                     from /usr/common/ftg/upcxx/2020.3.2/craype-2.6.2/knl/gnu/PrgEnv-gnu-6.0.5-8.3.0/upcxx.debug.gasnet_seq.aries/include/upcxx/future.hpp:7,
                     from /usr/common/ftg/upcxx/2020.3.2/craype-2.6.2/knl/gnu/PrgEnv-gnu-6.0.5-8.3.0/upcxx.debug.gasnet_seq.aries/include/upcxx/backend.hpp:5,
                     from /usr/common/ftg/upcxx/2020.3.2/craype-2.6.2/knl/gnu/PrgEnv-gnu-6.0.5-8.3.0/upcxx.debug.gasnet_seq.aries/include/upcxx/allocate.hpp:8,
                     from /usr/common/ftg/upcxx/2020.3.2/craype-2.6.2/knl/gnu/PrgEnv-gnu-6.0.5-8.3.0/upcxx.debug.gasnet_seq.aries/include/upcxx/upcxx.hpp:17,
                     from /global/homes/r/regan/workspace-shared/upcxx-utils/include/upcxx_utils/ofstream.hpp:17,
                     from /global/homes/r/regan/workspace-shared/upcxx-utils/src/ofstream.cpp:1:
    /usr/common/ftg/upcxx/2020.3.2/craype-2.6.2/knl/gnu/PrgEnv-gnu-6.0.5-8.3.0/upcxx.debug.gasnet_seq.aries/include/upcxx/future/impl_result.hpp:52:12: note: candidate: 'constexpr upcxx::detail::future_impl_result<>& upcxx::detail::future_impl_result<>::operator=(const upcxx::detail::future_impl_result<>&)'
         struct future_impl_result<> {
                ^~~~~~~~~~~~~~~~~~~~
    /usr/common/ftg/upcxx/2020.3.2/craype-2.6.2/knl/gnu/PrgEnv-gnu-6.0.5-8.3.0/upcxx.debug.gasnet_seq.aries/include/upcxx/future/impl_result.hpp:52:12: note:   no known conversion for argument 1 from 'std::remove_reference<upcxx::detail::future_impl_shref<upcxx::detail::future_header_ops_promise>&>::type' {aka 'upcxx::detail::future_impl_shref<upcxx::detail::future_header_ops_promise>'} to 'const upcxx::detail::future_impl_result<>&'
    /usr/common/ftg/upcxx/2020.3.2/craype-2.6.2/knl/gnu/PrgEnv-gnu-6.0.5-8.3.0/upcxx.debug.gasnet_seq.aries/include/upcxx/future/impl_result.hpp:52:12: note: candidate: 'constexpr upcxx::detail::future_impl_result<>& upcxx::detail::future_impl_result<>::operator=(upcxx::detail::future_impl_result<>&&)'
    /usr/common/ftg/upcxx/2020.3.2/craype-2.6.2/knl/gnu/PrgEnv-gnu-6.0.5-8.3.0/upcxx.debug.gasnet_seq.aries/include/upcxx/future/impl_result.hpp:52:12: note:   no known conversion for argument 1 from 'std::remove_reference<upcxx::detail::future_impl_shref<upcxx::detail::future_header_ops_promise>&>::type' {aka 'upcxx::detail::future_impl_shref<upcxx::detail::future_header_ops_promise>'} to 'upcxx::detail::future_impl_result<>&&'
    

  6. Dan Bonachea reporter

    auto fut = make_future();
    vs
    future<> fut = make_future();

    Correct - in 2020.3.0 and earlier, these two lines would result in different types, because the first would declare a "spooky future" that is implicitly convertible to future<>, but not vice versa. As of 2020.3.3, make_future always returns a "real" future and these two lines are equivalent.

  7. Dan Bonachea reporter

    I've just stumbled across a far worse consequence of this lack of compliance in our implementation.

    Here is the program:

    #include "../util.hpp"
    
    using namespace upcxx;
    
    int main(int argc, char **argv) {
      upcxx::init();
      print_test_header();
    
      future<> f  = make_future();
      auto     f2 = when_all(f,f); // compiles
      future<> f3 = when_all(f,f); // compiles
      auto     f4 = f2;            // compiles
      future<> f5 = f;             // compiles
      future<> f6(f2);             // OBSCURE TYPE ERROR
      future<> f7; 
      f7 = f2;                     // OBSCURE TYPE ERROR
    
      print_test_success();
      upcxx::finalize();
    }
    

    Every variable declared in the program above is unambiguously specified as having type future<>, which is also guaranteed to be CopyConstructible and CopyAssignable. However the two lines above that attempt to copy the result of when_all into a concrete future<> variable generate an obscure type error that will be inscrutable to our users. I believe this is due (at least in part) to the remaining non-compliance in the return value of when_all which returns a "spooky future" instead of the mandated concrete future type.

    This misbehavior dates back to at least 2019.9.0, and persists in develop. IMO this raises the priority level of this compliance defect to blocker status; unlike previously believed this defect is not just responsible for corner-case type inference failures, it's actually generating type errors for straightforward operations that are very clearly specified as permitted. Worse, AFAIK the only workaround is for users to avoid auto in declarations constructed with the result of when_all, which increases maintenance burden and completely defeats the stated purpose of the "spooky future" premature optimization.

    IMO we need to either properly implement implicit conversions necessary to make the program above compile without changes, or better yet do away with this misbegotten "spooky future" hack once and for all.

  8. Log in to comment