С++:Получите аргументы, полученные из std::bind
-
28-10-2019 - |
Вопрос
Сначала немного предыстории:В моей работе мы связываем обратные вызовы для последующего вызова, что может затруднить отслеживание потока управления через журналы.Чтобы помочь в этом, мы используем «контекст журнала», который позволяет вам отслеживать запрос по мере его прохождения через систему.Вы можете скопировать текущий контекст с помощью статической функции. log_context::get_current
и восстановите его с помощью статической функции log_context::set_current
.Это приводит к большому количеству повторяющегося кода каждый раз, когда мы отправляем обратные вызовы в рабочие очереди.
Я хотел бы создать функцию, которая является заменой для std::bind
это сохранит текущий log_context
и восстановить его при вызове.Однако у меня возникли некоторые проблемы с его написанием.
Прямо сейчас функция выглядит так:
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;
}
Это работает, но проблема в том, что использование требует передачи типа функции без какой-либо реальной причины (кроме того, как я узнаю, что использовать для TFuncArgs
):
bind_with_context(func_type(), &some_class::some_func, ptr, _1, "Bob", _2);
Так что это не совсем замена.Кажется, один должен знать эту информацию во время компиляции, я просто не могу понять, как это сделать.Это почти готово. Как избавиться от необходимости передавать тип функции?
Моя первоначальная мысль заключалась в том, чтобы разделить привязку и преобразовать ее в такую функцию:
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)...);
}
Проблема в том, что актерский состав (operator std::function<TReturn (TFuncArgs...)>() const
) никогда не будет вызываться (учитывая int foo(int x, int y, int z)
):
std::function<int (int)> f = bind_with_context(&foo, 4, 5, _1);
Причина в том, что function
конструктор пытается захватить operator ()
из context_binder
(хотя у него его нет).
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)’
Итак, мой вопрос для этого почти решения: Есть ли способ получить g++
предпочесть мой оператор изгнания вместо того, чтобы пытаться использовать function
конструктор?
Решение
Решение состоит в том, чтобы отделить привязку от преобразования в std::function
с использованием 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)...);
}
Расширение в TArgs
происходит, когда кто-то пытается назначить context_binder
для std::function
(чей конструктор для нецелого числа пытается захватить operator()
).