Boost logo

Boost :

Subject: Re: [boost] [local_function] passing local functions as template parameters
From: John Bytheway (jbytheway+boost_at_[hidden])
Date: 2010-09-12 13:40:16


On 12/09/10 16:41, Lorenzo Caminiti wrote:
> Hello all,
>
> Can you please check if the following mechanism can be used to pass
> local functor classes as template parameters?

<snip>

> SOLUTION (?)
>
> The following mechanism allows to define the functor body locally (in
> a local function which also binds the in-scope parameter `x`) and then
> pass it as a template parameter (to `boost::bind()` and then to
> `std::for_each()`). The trick is for the local function to override
> the virtual body of a global class and then pass the local function as
> a pointer to the global class while dynamic binding will actually
> execute the locally overridden body code.
>
> #include <boost/function.hpp>
> #include <boost/bind.hpp>
> #include <iostream>
> #include <algorithm>
> #include <vector>
>
> template<typename F> struct lfn_global {};
> template<typename A1> struct lfn_global<void (A1)> { virtual void body(A1) {} };
> // Similarly define `lfn_global` for other function arities and return types.
>
> int main () {
> double x = 5.6;
>
> struct lfn_t: lfn_global<void (int)> {
> lfn_t(double const& bound_x): x(bound_x) {}
> void body(int i) {
> std::cout << " " << i + x; // Local func body.
> }
> private:
> double const& x;
> } lfn_obj(x); // x is const& bound from scope.
> boost::function<void (int)> lfn = boost::bind(
> &lfn_global<void (int)>::body,
> (lfn_global<void (int)>*)(&lfn_obj),
> _1);
>
> std::vector<int> v;
> v.push_back(10);
> v.push_back(20);
> v.push_back(30);
>
> std::cout << "v: ";
> std::for_each(v.begin(), v.end(), lfn); // I can pass the local functor!
> std::cout << std::endl;
>
> return 0;
> }
>
> This code compiles on both GCC and MSVC, it correctly prints "v: 15.6
> 25.6 35.6". If this indeed works, I can hide all the extra code it
> requires behind the `BOOST_LOCAL_FUNCTION...` macros.
>
> Can you please try this code on other compilers?

Compiles fine on clang and icc.

> Do you see any issue with this code?

Yes, by using boost::function you're introducing an extra virtual
function call in each invocation of lfn. It's optimistic to assume that
the compiler will figure out what's going on well enough to optimize all
that away.

OTOH, I looked at the assembly generated by icc and it does indeed seem
to have arranged for both calls to be made directly, not through
vtables, so it is possible, but oddly the calls are not inlined (I guess
icc wasn't able to prove that there's only one call to lfn_t::body,
which it should in principle have been able to), so lfn_t::body is still
two calls away from main(). For definiteness, the piece of
Boost.Function called through is a 54-byte function including one
conditional branch.

I think it would be worth rolling your own simpler wrapper; something
along these lines:

#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include <algorithm>
#include <vector>

template<typename F> struct lfn_global {};
template<typename A1> struct lfn_global<void (A1)> {
  virtual void operator()(A1) {}
};
// Similarly define `lfn_global` for other function arities and return
types.

template<typename F>
struct ref_wrapper;

template<typename A1>
struct ref_wrapper<void (A1)> {
  typedef lfn_global<void (A1)> wrapped;
  explicit ref_wrapper(wrapped& f) : f_(f) {}
  void operator()(A1 a1) {
    f_(a1);
  }
  wrapped& f_;
};

int main () {
    double x = 5.6;

    struct lfn_t: public lfn_global<void (int)> {
        lfn_t(double const& bound_x): x(bound_x) {}
        void operator()(int i) {
            std::cout << " " << i + x; // Local func body.
        }
    private:
        double const& x;
    } lfn_obj(x); // x is const& bound from scope.
    ref_wrapper<void (int)> lfn(lfn_obj);

    std::vector<int> v;
    v.push_back(10);
    v.push_back(20);
    v.push_back(30);

    std::cout << "v: ";
    std::for_each(v.begin(), v.end(), lfn);
    std::cout << std::endl;

    return 0;
}

(I would have used boost::ref, but that doesn't overload operator())

In this case icc chooses to inline the call to ref_wrapper::operator(),
so that lfn_t::operator() is called directly from main (as before, that
one is still not inlined).

Anyway, the upshot of what I'm trying to say is: you may wish to make
the compiler's life easier to head off efficiency concerns, and you
should certainly do some profiling to measure the cost of a virtual
function call in this context. But, all in all, it's a neat trick.

John Bytheway


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk