4

How can I capture return values from boost::asio::io_service? Is it possible to use some bind or any simple construct for that that doesn't involve rewriting functions?

The following is a minimal example. The I'm trying to capture the value return of GetSum():

#include <iostream>
#include <boost/asio.hpp>
#include <functional>

using namespace std;

void SayHello()
{
    std::cout<<"Hello!"<<std::endl;
}

template <typename T>
T GetSum(T a, T b)
{
    std::cout<<"Adding " << a << " and " << b << std::endl;
    return a+b;
}

int main(int argc, char *argv[])
{
    boost::asio::io_service ioservice;

    ioservice.post(&SayHello);
    ioservice.post(std::bind(&GetSum<double>,1,2));

    ioservice.run();
    return 0;
}

Why? Because I'm desining a thread-pool, and I'm thinking of my options to make it possible for the user to get return values of his functions without having to wrap his functions with another function manually that will capture the return value for him.

My solution:

int main(int argc, char *argv[])
{
    boost::asio::io_service ioservice;

    ioservice.post(&SayHello);
    double sum;
    ioservice.post([&sum]()
    {
        sum = GetSum(1,2);
    });

    ioservice.run();
    std::cout<< sum <<std::endl; //is 3
    return 0;
}

But I'm still wishing there's something simpler with a bind, or something.

The Quantum Physicist
  • 24,987
  • 19
  • 103
  • 189
  • 1
    It seems that you need some kind of [`std::future`](http://en.cppreference.com/w/cpp/thread/future) – Teivaz Mar 20 '17 at 09:13
  • 1
    @teivaz Sounds about right, but I thought futures are made explicitly for `std::thread`. It's part of the thread library. – The Quantum Physicist Mar 20 '17 at 09:19
  • I think in addition to your solution I would also capture some kind of `ManualResetEvent` (or a mutex + cv) which is set inside the io_service when the operation has finished. That enables the submitter to wait for the result of the operation (and not only to wait until all queued operations have finished - as it happens with `ioservice.run()`. Returning a `std/boost::future` which is completed inside the ioservice thread would allow the same. – Matthias247 Mar 20 '17 at 09:54
  • @Matthias247 Actually I'm not familiar with Windows API, so I don't know what `ManualResetEvent` does. I have to read about it. Anyway, waiting has to definitely be done the way you mentioned. The examples are just minimal :) – The Quantum Physicist Mar 20 '17 at 09:57
  • Possibly related: http://stackoverflow.com/a/22430940/3962537 | There's a number of questions similar to this. A while ago I've implemented my own wrappers to accomplish something similar, using `boost::future` and `boost::promise`. – Dan Mašek Mar 20 '17 at 17:50

4 Answers4

2

I came up with a solution inspired from the suggestions to use something like std::future. So I used std::future, and the code works.

What I did is simply inherit from io_service, and create a new method post_with_future that has a future of the return value. I would appreciate critiquing this solution to improve it.

#include <iostream>
#include <functional>
#include <type_traits>
#include <future>
#include <boost/asio.hpp>

class future_io_service : public boost::asio::io_service
{
public:
    template <typename FuncType>
    std::future<typename std::result_of<FuncType()>::type> post_with_future(FuncType&& func)
    {
        //keep in mind that std::result_of is std::invoke_result in C++17
        typedef typename std::result_of<FuncType()>::type return_type;
        typedef typename std::packaged_task<return_type()> task_type;
        //since post requires that the functions in it are copy-constructible, we use a shared pointer for the packaged_task since it's only movable and non-copyable
        std::shared_ptr<task_type> task = std::make_shared<task_type>(std::move(func));
        std::future<return_type> returned_future = task->get_future();
        this->post(std::bind(&task_type::operator(),task));
        return returned_future;
    }
};

void SayHello()
{
    std::cout<<"Hello!"<<std::endl;
}

template <typename T>
T GetSum(T a, T b)
{
    std::cout<<"Adding " << a << " and " << b << std::endl;
    return a+b;
}

int main()
{
    future_io_service ioservice;

    ioservice.post(&SayHello);
    auto sum = ioservice.post_with_future(std::bind(&GetSum<int>,1,2));
    ioservice.run();
    std::cout<<sum.get()<<std::endl; //result is 3
    return 0;
}
The Quantum Physicist
  • 24,987
  • 19
  • 103
  • 189
1

This is how you can do it by making use of asio::use_future and async_result. Note that, I have kept the example simple by passing things by value and using hard coded arguments for the sum.

#include <iostream>
#include <thread>
#include <asio.hpp>
#include <asio/use_future.hpp>

int get_sum(int a, int b)
{
  return a + b;
}

template <typename Func, typename CompletionFunction>
auto perform_asyncly(asio::io_service& ios, Func f, CompletionFunction cfun)
{

  using handler_type = typename asio::handler_type
                         <CompletionFunction, void(asio::error_code, int)>::type;

  handler_type handler{cfun};
  asio::async_result<handler_type> result(handler);

  ios.post([handler, f]() mutable {
        handler(asio::error_code{}, f(2, 2));
      });

  return result.get();
}

int main() {
  asio::io_service ios;
  asio::io_service::work wrk{ios};
  std::thread t{[&]{ ios.run(); }};
  auto res = perform_asyncly(ios, get_sum, asio::use_future);
  std::cout << res.get() << std::endl;

  t.join();

  return 0;
}
Arunmu
  • 6,837
  • 1
  • 24
  • 46
  • Not criticizing, but you haven't followed any of the terms of the example I provided. You haven't even supplied the parameters of `get_sum` in the call! I appreciate you telling me about `use_future` and `async_result`. I'm going to research about them. – The Quantum Physicist Mar 22 '17 at 07:13
  • 2
    Yes, I know. I really did not want to put that much effort which I feel is trivial. I just wanted to convey that there is something called as `async_result` and `use_future`. – Arunmu Mar 22 '17 at 08:13
0

If the goal is to have a simple one liner bind like function that also captures for you the return value, you could implement it like this:

#include <iostream>
#include <boost/asio.hpp>
#include <functional>

using namespace std;

void SayHello()
{
  std::cout<<"Hello!"<<std::endl;
}

template <typename T>
T GetSum(T a, T b)
{
  std::cout<<"Adding " << a << " and " << b << std::endl;
  return a+b;
}

template<typename R, typename F, typename... Args>
auto bind_return_value(R& r, F&& f, Args&&... args)
{
  return [&]()
    {
      r = f(std::forward<Args>(args)...);
    };
}

int main(int argc, char *argv[])
{
  boost::asio::io_service ioservice;

  ioservice.post(&SayHello);
  double sum;
  ioservice.post(bind_return_value(sum, &GetSum<double>, 1, 2));

  ioservice.run();
  std::cout<< sum <<std::endl; //is 3
  return 0;
}
Drax
  • 12,682
  • 7
  • 45
  • 85
  • Thanks for the answer. Actually I was looking for something like this, but as a part of the standard. Let's wait and see if something better comes up :) – The Quantum Physicist Mar 20 '17 at 09:52
  • @TheQuantumPhysicist Sure, this answer is the result of me failing to do that with only standard tools, but maybe someone will find a trick without implementing a new function :) – Drax Mar 20 '17 at 09:56
0

The below solution is what I plan to use in my own application. Three features:

  1. Functions/lambdas are arguments to post_function_use_future(). Requirements: Functions must return a value other than void, and they must have zero input arguments. Note that SayHello() returns an int now.

  2. Any Asio context can be used, such as io_context and strands.

  3. No deprecated functions as of this writing.

In the main cpp file:

#include <iostream>
#include <thread>
#include <boost/asio.hpp>
#include "function_return_type.hpp"

template <typename ExecutionContext, typename FuncWithReturnNoArgs>
auto post_function_use_future(ExecutionContext& ctx, FuncWithReturnNoArgs f)
{
    using handler_type = typename boost::asio::handler_type
        <boost::asio::use_future_t<>, void(boost::system::error_code, return_type_t<FuncWithReturnNoArgs>)>::type;

    using Sig = void(boost::system::error_code, return_type_t<FuncWithReturnNoArgs>);
    using Result = typename boost::asio::async_result<boost::asio::use_future_t<>, Sig>;
    using Handler = typename Result::completion_handler_type;

    Handler handler(std::forward<decltype(boost::asio::use_future)>(boost::asio::use_future));
    Result result(handler);

    boost::asio::post(ctx, [handler, f]() mutable {
        handler(boost::system::error_code(), f());
    });

    return result.get();
}

namespace asio = boost::asio;

int SayHello()
{
    std::cout << "Hello!" << std::endl;
    return 0;
}

template <typename T>
T GetSum(T a, T b)
{
    std::cout << "Adding " << a << " and " << b << std::endl;
    return a + b;
}

int main() {
    asio::io_context io;
    auto wg = asio::make_work_guard(io);

    std::thread t{ [&] { io.run(); } };

    auto res1 = post_function_use_future(io, SayHello);
    res1.get(); // block until return value received.

    auto res2 = post_function_use_future(io, []() {return  GetSum(20, 14); });
    std::cout << res2.get() << std::endl; // block until return value received.

    wg.reset();
    if(t.joinable()) t.join();

    return 0;
}

In the function_return_type.hpp file (Huge kudos to this solution):

#ifndef FUNCTION_RETURN_TYPE_HPP
#define FUNCTION_RETURN_TYPE_HPP

template <typename F>
struct return_type_impl;

template <typename R, typename... Args>
struct return_type_impl<R(Args...)> { using type = R; };

template <typename R, typename... Args>
struct return_type_impl<R(Args..., ...)> { using type = R; };

template <typename R, typename... Args>
struct return_type_impl<R(*)(Args...)> { using type = R; };

template <typename R, typename... Args>
struct return_type_impl<R(*)(Args..., ...)> { using type = R; };

template <typename R, typename... Args>
struct return_type_impl<R(&)(Args...)> { using type = R; };

template <typename R, typename... Args>
struct return_type_impl<R(&)(Args..., ...)> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args...)> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args..., ...)> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args...) &> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args..., ...) &> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args...) && > { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args..., ...) && > { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args...) const> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args..., ...) const> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args...) const&> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args..., ...) const&> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args...) const&&> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args..., ...) const&&> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args...) volatile> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args..., ...) volatile> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args...) volatile&> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args..., ...) volatile&> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args...) volatile&&> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args..., ...) volatile&&> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args...) const volatile> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args..., ...) const volatile> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args...) const volatile&> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args..., ...) const volatile&> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args...) const volatile&&> { using type = R; };

template <typename R, typename C, typename... Args>
struct return_type_impl<R(C::*)(Args..., ...) const volatile&&> { using type = R; };

template <typename T, typename = void>
struct return_type
    : return_type_impl<T> {};

template <typename T>
struct return_type<T, decltype(void(&T::operator()))>
    : return_type_impl<decltype(&T::operator())> {};

template <typename T>
using return_type_t = typename return_type<T>::type;

#endif
Andrew Dolder
  • 386
  • 3
  • 4