Returning multiple values from a C++ function

后端 未结 21 2519
别跟我提以往
别跟我提以往 2020-11-22 01:04

Is there a preferred way to return multiple values from a C++ function? For example, imagine a function that divides two integers and returns both the quotient and the rema

21条回答
  •  悲哀的现实
    2020-11-22 01:51

    There are a bunch of ways to return multiple parameters. I'm going to be exhastive.

    Use reference parameters:

    void foo( int& result, int& other_result );
    

    use pointer parameters:

    void foo( int* result, int* other_result );
    

    which has the advantage that you have to do a & at the call-site, possibly alerting people it is an out-parameter.

    Write a template and use it:

    template
    struct out {
      std::function target;
      out(T* t):target([t](T&& in){ if (t) *t = std::move(in); }) {}
      out(std::optional* t):target([t](T&& in){ if (t) t->emplace(std::move(in)); }) {}
      out(std::aligned_storage_t* t):
        target([t](T&& in){ ::new( (void*)t ) T(std::move(in)); } ) {}
      template // TODO: SFINAE enable_if test
      void emplace(Args&&...args) {
        target( T(std::forward(args)...) );
      }
      template // TODO: SFINAE enable_if test
      void operator=(X&&x){ emplace(std::forward(x)); }
      template // TODO: SFINAE enable_if test
      void operator()(Args...&&args){ emplace(std::forward(args)...); }
    };
    

    then we can do:

    void foo( out result, out other_result )
    

    and all is good. foo is no longer able to read any value passed in as a bonus.

    Other ways of defining a spot you can put data can be used to construct out. A callback to emplace things somewhere, for example.

    We can return a structure:

    struct foo_r { int result; int other_result; };
    foo_r foo();
    

    whick works ok in every version of C++, and in c++17 this also permits:

    auto&&[result, other_result]=foo();
    

    at zero cost. Parameters can even not even be moved thanks to guaranteed elision.

    We could return a std::tuple:

    std::tuple foo();
    

    which has the downside that parameters are not named. This permits the c++17:

    auto&&[result, other_result]=foo();
    

    as well. Prior to c++17 we can instead do:

    int result, other_result;
    std::tie(result, other_result) = foo();
    

    which is just a bit more awkward. Guaranteed elision doesn't work here, however.

    Going into stranger territory (and this is after out<>!), we can use continuation passing style:

    void foo( std::function );
    

    and now callers do:

    foo( [&](int result, int other_result) {
      /* code */
    } );
    

    a benefit of this style is you can return an arbitrary number of values (with uniform type) without having to manage memory:

    void get_all_values( std::function value )
    

    the value callback could be called 500 times when you get_all_values( [&](int value){} ).

    For pure insanity, you could even use a continuation on the continuation.

    void foo( std::function)> result );
    

    whose use looks like:

    foo( [&](int result, auto&& other){ other([&](int other){
      /* code */
    }) });
    

    which would permit many-one relationships between result and other.

    Again with uniforn values, we can do this:

    void foo( std::function< void(span) > results )
    

    here, we call the callback with a span of results. We can even do this repeatedly.

    Using this, you can have a function that efficiently passes megabytes of data without doing any allocation off the stack.

    void foo( std::function< void(span) > results ) {
      int local_buffer[1024];
      std::size_t used = 0;
      auto send_data=[&]{
        if (!used) return;
        results({ local_buffer, used });
        used = 0;
      };
      auto add_datum=[&](int x){
        local_buffer[used] = x;
        ++used;
        if (used == 1024) send_data();
      };
      auto add_data=[&](gsl::span xs) {
        for (auto x:xs) add_datum(x);
      };
      for (int i = 0; i < 7+(1<<20); ++i) {
        add_datum(i);
      }
      send_data(); // any leftover
    }
    

    Now, std::function is a bit heavy for this, as we would be doing this in zero-overhead no-allocation environments. So we'd want a function_view that never allocates.

    Another solution is:

    std::function)> foo(int input);
    

    where instead of taking the callback and invoking it, foo instead returns a function which takes the callback.

    foo(7)([&](int result, int other_result){ /* code */ });
    

    this breaks the output parameters from the input parameters by having separate brackets.

    With variant and c++20 coroutines, you could make foo a generator of a variant of the return types (or just the return type). The syntax is not yet fixed, so I won't give examples.

    In the world of signals and slots, a function that exposes a set of signals:

    template
    struct broadcaster;
    
    broadcaster foo();
    

    allows you to create a foo that does work async and broadcasts the result when it is finished.

    Down this line we have a variety of pipeline techniques, where a function doesn't do something but rather arranges for data to be connected in some way, and the doing is relatively independant.

    foo( int_source )( int_dest1, int_dest2 );
    

    then this code doesn't do anything until int_source has integers to provide it. When it does, int_dest1 and int_dest2 start recieving the results.

提交回复
热议问题