3

First, a little background: At my work we bind callbacks to be invoked later, which can make trying to follow control flow through the logs quite difficult. To help this, we use a "log context," which lets you follow a request as it goes through the system. You can copy out the current context with a static function log_context::get_current and restore it with the static function log_context::set_current. This leads to a leads to a lot of repeated code every time we post callbacks to worker queues.

I would like to make a function which is a drop-in replacement for std::bind that will save the current log_context and restore it when invoked. However, I'm having some trouble writing it.

Right now, the function looks like this:

template <typename TResult,
          typename... TFuncArgs,
          typename Func,
          typename... TProvidedArgs
         >
std::function<TResult (TFuncArgs...)>
bind_with_context(const std::function<TResult (TFuncArgs...)>& throwaway,
                  Func func,
                  TProvidedArgs&&... args
                 )
{
    log_context cxt = log_context::get_current();
    auto bound = std::bind(func, std::forward<TProvidedArgs>(args)...);
    auto lambda = [cxt, bound] (TFuncArgs... args) -> TResult
                  {
                      log_context::set_current(cxt);
                      return bound(args...);
                  };
    return lambda;
}

It works, but the problem is the usage requires you to pass the function type for no real reason (aside from that's how I find out what to use for TFuncArgs):

bind_with_context(func_type(), &some_class::some_func, ptr, _1, "Bob", _2);

So, not quite a drop-in replacement. It seems like one should know this information at compile-time, I just can't figure out how. It is almost there. How can I eliminate the need to pass in the type of function?


My initial thought was to split the binding from converting it to a function like so:

template <typename Func>
struct context_binder
{
public:
    context_binder(const Func& func) :
            func(func)
    { }

    // Use the casting operator to figure out what we're looking for:
    template <typename TReturn, typename... TFuncArgs>
    operator std::function<TReturn (TFuncArgs...)>() const
    {
        log_context cxt = log_context::get_current();
        auto lambda = [func, cxt] (TFuncArgs... args) -> TReturn
                      {
                          log_context::set_current(cxt);
                          return func(std::forward<TFuncArgs>(args)...);
                      };
        return lambda;
    }

private:
    Func func;
};

template <typename F, typename... TArgs>
auto bind_with_context(F f, TArgs&&... args)
        -> context_binder<decltype(std::bind(f, std::forward<TArgs>(args)...))>
{
    return std::bind(f, std::forward<TArgs>(args)...);
}

The problem is that the cast (operator std::function<TReturn (TFuncArgs...)>() const) will never be called (given int foo(int x, int y, int z)):

std::function<int (int)> f = bind_with_context(&foo, 4, 5, _1);

The reason is that functions constructor is trying to grab operator () from the context_binder (even though it doesn't have one).

In file included from scratch.cpp:1:0:
/usr/local/include/gcc-4.6.2/functional: In static member function ‘static _Res std::_Function_handler<_Res(_ArgTypes ...), _Functor>::_M_invoke(const std::_Any_data&, _ArgTypes ...) [with _Res = int, _Functor = context_binder<std::_Bind<int (*(int, int, std::_Placeholder<1>))(int, int, int)> >, _ArgTypes = {int}]’:
/usr/local/include/gcc-4.6.2/functional:2148:6:   instantiated from ‘std::function<_Res(_ArgTypes ...)>::function(_Functor, typename std::enable_if<(! std::is_integral<_Functor>::value), std::function<_Res(_ArgTypes ...)>::_Useless>::type) [with _Functor = context_binder<std::_Bind<int (*(int, int, std::_Placeholder<1>))(int, int, int)> >, _Res = int, _ArgTypes = {int}, typename std::enable_if<(! std::is_integral<_Functor>::value), std::function<_Res(_ArgTypes ...)>::_Useless>::type = std::function<int(int)>::_Useless]’
scratch.cpp:53:85:   instantiated from here
/usr/local/include/gcc-4.6.2/functional:1764:40: error: no match for call to ‘(context_binder<std::_Bind<int (*(int, int, std::_Placeholder<1>))(int, int, int)> >) (int)’

So my question for this almost solution is: Is there any way to get g++ to prefer my cast-out operator instead of trying to use function's constructor?

Travis Gockel
  • 26,877
  • 14
  • 89
  • 116
  • 2
    Why not replace `const std::function& throwaway, Func func` with `TResult (Class::*)(TFuncArgs...)`. Then result type will be deducable. – Lol4t0 Feb 21 '12 at 18:32
  • I thought about that...you can bind to non-member functions, with `const` and `volatile` qualifiers on the `Class`. Having 9 overloads isn't terrible. – Travis Gockel Feb 21 '12 at 18:46
  • 1
    It's a lot more than 9 overloads if your compiler supports [move semantics for `*this`](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2439.htm). – ildjarn Feb 21 '12 at 18:54
  • @ildjarn: Would that be `TResult (Class::*)(TFuncArgs...) &&`? Honestly, once you get into macro-land it's just a matter of adding `entry(&&) entry(const &&)`, etc, etc. – Travis Gockel Feb 21 '12 at 19:01
  • `&&`, `&`, and all permutations of those with `const` and `volatile`. It's a lot of overloads. – ildjarn Feb 21 '12 at 19:02
  • It's a lot of overloads, but it *is* a fixed amount. **O(27) ~~ O(1)** – Travis Gockel Feb 21 '12 at 19:04
  • According to the spec, I think your example should have given an ambiguity instead of simply using the ctor of the std::function<> object. Hmm. – Johannes Schaub - litb Feb 22 '12 at 19:05
  • @JohannesSchaub: It would probably be less ambiguous to the reader, but compiler will unambiguously call `template function(F)`. – Travis Gockel Feb 23 '12 at 05:01
  • @travis i think that behavior is incorrect. – Johannes Schaub - litb Feb 28 '12 at 09:46

1 Answers1

3

The solution is to separate the binding from the converting to an std::function using operator ():

template <typename Func>
struct context_binder
{
private:
    Func        func;
    log_context cxt;

public:
    context_binder(const Func& func) :
            func(func),
            cxt(log_context::get_current())
    { }

    template <typename... TArgs>
    auto operator ()(TArgs&&... args) const
            -> decltype(func(std::forward<TArgs>(args)...))
    {
        log_context::set_current(cxt);
        return func(std::forward<TArgs>(args)...);
    }
};

template <typename F, typename... TArgs>
auto bind_with_context(F f, TArgs&&... args)
        -> context_binder<decltype(std::bind(f, std::forward<TArgs>(args)...))>
{
    return std::bind(f, std::forward<TArgs>(args)...);
}

The expansion into TArgs happen when someone tries to assign a context_binder to an std::function (whose constructor for non-integral tries to grab operator()).

Travis Gockel
  • 26,877
  • 14
  • 89
  • 116
  • I'm going to leave this question open because this answer isn't prettiest thing in the world. – Travis Gockel Feb 21 '12 at 21:44
  • It does on my compiler (g++ 4.6.2). – Travis Gockel Feb 22 '12 at 05:39
  • Your solution really is adequate. There is currently no way to define a functor taking a variadic number of arguments inline -- no amount of lambda expressions or `std::bind` can solve that, especially considering that a call to `std::bind` 'fixes' the arity and nested bind expressions have a special meaning. – Luc Danton Feb 22 '12 at 14:25