6

In trying to understand the cirumstances under which std::bind allocates memory, I looked at this answer, which gives some intuition, but I wanted a more detailed understanding, so I went and looked at the source for gcc.

I am examining the following source code for std::bind from the gcc implementation of the C++ standard library.

  /**
   *  @brief Function template for std::bind.
   *  @ingroup binders
   */
  template<typename _Func, typename... _BoundArgs>
    inline typename
    _Bind_helper<__is_socketlike<_Func>::value, _Func, _BoundArgs...>::type
    bind(_Func&& __f, _BoundArgs&&... __args)
    {
      typedef _Bind_helper<false, _Func, _BoundArgs...> __helper_type;
      typedef typename __helper_type::__maybe_type __maybe_type;
      typedef typename __helper_type::type __result_type;
      return __result_type(__maybe_type::__do_wrap(std::forward<_Func>(__f)),
               std::forward<_BoundArgs>(__args)...);
    }

Given a function F and parameters A and B, where can I find the code that copies them into the returned data structure, or is this compiler generated?

Community
  • 1
  • 1
merlin2011
  • 71,677
  • 44
  • 195
  • 329

2 Answers2

1

This line:

__result_type(__maybe_type::__do_wrap(std::forward<_Func>(__f)), std::forward<_BoundArgs>(__args)...);

Parameters, both the callable (wrapped with __do_wrap) and the arguments, are forwarded to the __result_type type that stores them likely in a data structure.

You should look for the code __result_type, it wraps the data returned by the former in the implementation defined type mentioned two lines above (that is _Bind_helper<false, _Func, _BoundArgs...>::type).
The actual type is _Bind (search class _Bind), which has a constructor that accepts both the callable and the arguments and, of course, is a template class (exposed by means of a few helpers around).
In fact, the Bind_helper<whatever>::type (that is the returned type) is defined as typedef _Bind<whatever> type, you can look for that class and that's all.

skypjack
  • 49,335
  • 19
  • 95
  • 187
  • I already looked at [`__do_wrap`](https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/std/functional#L875) which is a one-liner that does not appear to allocate memory unless it does so implicitly. Result type seems to be some kind of type generated from templates. – merlin2011 Apr 14 '16 at 05:45
  • Sorry, ignore `__do_wrap`, my fault. The key seems `__result_type`. – skypjack Apr 14 '16 at 05:46
  • If I'm understanding it correctly the final class that you're referring to may not exist in source form? – merlin2011 Apr 14 '16 at 06:19
  • It's in the source file you linked, indeed. – skypjack Apr 14 '16 at 06:22
  • 2
    @merlin2011: just in case you're looking for it: you won't find any *dynamic* memory allocation; the `_Bind` constructor uses the template types to create data members, which are populated by the constructor. `bind` then returns by value, so it's up to the caller to have left an area of stack space to be populated by the returned value - it will be reserved by the same adjustment to the stack pointer that made space for e.g. local variables - there's no "allocation" machine code dedicated to this exclusively. – Tony Delroy Apr 14 '16 at 06:22
  • @tonyd, That's extremely helpful to know. Now I'm looking at the constructor but I do not understand how and where the data members are created using the templates. If you expand your comment into an answer I think it would be very instructive. – merlin2011 Apr 14 '16 at 06:33
  • 1
    @merlin2011 the `bind()` return type ends up being `_Bind<__func_type(typename decay<_BoundArgs>::type...)>` - the `_Bind` constructors stash the values in data members, e.g. `_Bind(const _Functor& __f, _Args&&... __args) : _M_f(__f), _M_bound_args(std::forward<_Args>(__args)...)`. – Tony Delroy Apr 14 '16 at 06:49
1

Nowhere are we allocating memory. We're just creating an object with type specific to this particular bind() that has a function member (possibly wrapped into some other type and copied/moved as appropriate) and a tuple of the arguments (copied/moved as appropriate).

The standard does not specify anything about memory allocation, but there's no need to do such a thing for bind() so any good implementation won't.


This overload is returning a:

_Bind_helper<__is_socketlike<_Func>::value, _Func, _BoundArgs...>::type

This overload only participates in overload resolution if __is_socketlike<_Func>::value is false. In that case, this type is:

typedef _Maybe_wrap_member_pointer<typename decay<_Func>::type> __maybe_type;
typedef typename __maybe_type::type __func_type;
typedef _Bind<__func_type(typename decay<_BoundArgs>::type...)> type;

Ignoring the template argument meaning, we're basically constructing something of type:

template<typename _Signature>
  struct _Bind;

template<typename _Functor, typename... _Bound_args>
 class _Bind<_Functor(_Bound_args...)>
 : public _Weak_result_type<_Functor>
 {
     ...
 };

which has these members:

_Functor _M_f;
tuple<_Bound_args...> _M_bound_args;

and these relevant constructors:

  template<typename... _Args>
explicit _Bind(const _Functor& __f, _Args&&... __args)
: _M_f(__f), _M_bound_args(std::forward<_Args>(__args)...)
{ }

  template<typename... _Args>
explicit _Bind(_Functor&& __f, _Args&&... __args)
: _M_f(std::move(__f)), _M_bound_args(std::forward<_Args>(__args)...)
{ }
Barry
  • 286,269
  • 29
  • 621
  • 977