2

This is a boost::asio udp echo demo based on a boost asio example.

The meat of this version using C++ lambda is less than half the size of the boost example one, but gcc tells me that received is not visible in recv_from.

It pains me to have to write this in a more verbose manner. Can some C++ guru help me with a trick to define mutually recursive lambdas?

class server {
public:
  server(io_service& io_service, short port)
    : socket_(io_service, udp::endpoint(udp::v4(), port)) {
    auto recv_from = [&,received]() {
      socket_.async_receive_from(buffer(data_, max_length), sender_endpoint_,
                                 received);
    };
    auto received = [&,recv_from](const error_code& error, size_t bytes_transferred) {
      if (!error && bytes_transferred > 0) {
        socket_.async_send_to(buffer(data_, bytes_transferred), sender_endpoint_,
                              [&](const error_code&, size_t) {
                                recv_from();
                              });
      } else {
        recv_from(); // loop
      }
    };
    recv_from();
  }

private:
  udp::socket socket_;
  udp::endpoint sender_endpoint_;
  enum { max_length = 1024 };
  char data_[max_length];
};

Edit, solution: I needed to add this:

std::function<void(const error_code&, size_t)> received;

to make it easy for the type-inference engine (I'm spoiled having programmed Haskell)

Edit2: There are lifetime issues so that won't work.

user239558
  • 6,964
  • 1
  • 28
  • 35
  • How about you use normal `static` functions? Problem gone, more readable. – Seth Carnegie Mar 13 '13 at 19:33
  • I see that my question is probably a duplicate of this one: http://stackoverflow.com/questions/2067988/recursive-lambda-functions-in-c0x?rq=1 – user239558 Mar 13 '13 at 19:34
  • +Seth Carnegie With static functions it is less readable because there is 50% more boilerplate. – user239558 Mar 13 '13 at 19:35
  • Oh, I see you are using the capture. Then use non-static functions. There's hardly any extra boiler plate, if any. – Seth Carnegie Mar 13 '13 at 19:38
  • Look at the link at the beginning of the question. I count 40 vs 18 lines. – user239558 Mar 13 '13 at 19:40
  • Their code is doing a lot more than yours is. Or in a different way that takes more lines. – Seth Carnegie Mar 13 '13 at 19:45
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/26117/discussion-between-user239558-and-seth-carnegie) – user239558 Mar 13 '13 at 19:46
  • 1
    Note that `std::function` has costs (it is dynamically dispatched). In addition, instead of editing your question with the answer, self-answer your question, then mark it as the correct answer. :) – Yakk - Adam Nevraumont Mar 13 '13 at 19:47
  • Yakk, that begs the question: How do you do recursive lambdas with little/no cost? (given that the scope holding the lambda doesn't exist) – user239558 Mar 13 '13 at 20:11
  • There is a reoccurring theme of readability between the question, comments, and chat logs. An alternative solution to less verbosity and arguable more readability is stackless coroutines. Boost.Asio provides an [example](http://www.boost.org/doc/libs/1_53_0/doc/html/boost_asio/example/http/server4/). Additionally, Christopher Kohlhoff, the author of Boost.Asio, has a few blogs entries about this approach on his [blog](http://blog.think-async.com/2009/08/composed-operations-coroutines-and-code.html). – Tanner Sansbury Mar 14 '13 at 05:01
  • twansbury, those coroutines are fantastic! Thanks! My only issue with the last blog post is that he could probably do more with lambdas, and less with less boost::bind magic. – user239558 Mar 14 '13 at 11:23

1 Answers1

4

Answering my own question:

There are actually no less than three problems with my code.

  1. I have been careful to copy the received and recv_from into the corresponding closures so that they would be available when the constructor goes out of scope. Unfortunately, the closures go out of scope at the same time as the constructor. Thus the [&, xxx] copying of xxx makes no sense.

  2. The type of at least(?) one of the lambdas must be fixed to please the type inference engine.

  3. But that doesn't solve issue #1. To fix the lifetime issue, I should have stored the closure objects in the server object.

So I think this is close to what I need to do:

class server {
 public:
   server(io_service& io_service, short port)
     : socket_(io_service, udp::endpoint(udp::v4(), port)) {
    recv_from = [&]() {
       socket_.async_receive_from(buffer(data_, max_length), sender_endpoint_,
                                 received);
    };
    received = [&](const error_code& error, size_t bytes_transferred) {
      if (!error && bytes_transferred > 0) {
        socket_.async_send_to(buffer(data_, bytes_transferred), sender_endpoint_,
                              [&](const error_code&, size_t) {
                                recv_from();
                              });
      } else {
        recv_from(); // loop
      }
    };
    recv_from();
  }

private:
  udp::socket socket_;
  udp::endpoint sender_endpoint_;
  std::function<void(const error_code&, size_t)> received;
  std::function<void()> recv_from;
  enum { max_length = 1024 };
  char data_[max_length];
};
Community
  • 1
  • 1
user239558
  • 6,964
  • 1
  • 28
  • 35