1

Let me start with explaining what I try to accomplish. I need to create a type-erased functor (using templates and virtual functions) that would be able to "emplace" a new object in the storage of message queue for the RTOS I'm developing. Such "trickery" is required, because I want most of message queue's code to be non-templated, with only the parts that really need the type info implemented as such type-erased functors. This is a project for embedded microcontrollers (*), so please assume that I just cannot make whole message queue with a template, because ROM space is not unlimited in such environment.

I already have functors that can "copy-construct" and "move-construct" the object into the queue's storage (for "push" operations) and I also have a functor that can "swap" the object out of the queue's storage (for "pop" operations). To have a complete set I need a functor that will be able to "emplace" the object into the queue's storage.

So here is the minimum example that exhibits the problem I'm facing with creating it. Do note that this is a simplified scenario, which doesn't show much of the boiler plate (there are no classes, no inheritance and so on), but the error is exactly the same, as the root cause is probably the same too. Also please note, that the use of std::bind() (or a similar mechanism that would NOT use dynamic allocation) is essential to my use case.

#include <functional>

template<typename T, typename... Args>
void emplacer(Args&&... args)
{
    T value {std::forward<Args>(args)...};
}

template<typename T, typename... Args>
void emplace(Args&&... args)
{
    auto boundFunction = std::bind(emplacer<T, Args...>,
            std::forward<Args>(args)...);
    boundFunction();
}

int main()
{
    int i = 42;
    emplace<int>(i);        // <---- works fine

    emplace<int>(42);       // <---- doesn't work...
}

When compiled on PC with g++ -std=c++11 test.cpp the first instantiation (the one using a variable) compiles with no problems, but the second one (which uses a constant 42 directly) throws this error messages:

test.cpp: In instantiation of ‘void emplace(Args&& ...) [with T = int; Args = {int}]’:
test.cpp:21:17:   required from here
test.cpp:13:16: error: no match for call to ‘(std::_Bind<void (*(int))(int&&)>) ()’
  boundFunction();
                ^
In file included from test.cpp:1:0:
/usr/include/c++/4.9.2/functional:1248:11: note: candidates are:
     class _Bind<_Functor(_Bound_args...)>
           ^
/usr/include/c++/4.9.2/functional:1319:2: note: template<class ... _Args, class _Result> _Result std::_Bind<_Functor(_Bound_args ...)>::operator()(_Args&& ...) [with _Args = {_Args ...}; _Result = _Result; _Functor = void (*)(int&&); _Bound_args = {int}]
  operator()(_Args&&... __args)
  ^
/usr/include/c++/4.9.2/functional:1319:2: note:   template argument deduction/substitution failed:
/usr/include/c++/4.9.2/functional:1315:37: error: cannot bind ‘int’ lvalue to ‘int&&’
  = decltype( std::declval<_Functor>()(
                                     ^
/usr/include/c++/4.9.2/functional:1333:2: note: template<class ... _Args, class _Result> _Result std::_Bind<_Functor(_Bound_args ...)>::operator()(_Args&& ...) const [with _Args = {_Args ...}; _Result = _Result; _Functor = void (*)(int&&); _Bound_args = {int}]
  operator()(_Args&&... __args) const
  ^
/usr/include/c++/4.9.2/functional:1333:2: note:   template argument deduction/substitution failed:
/usr/include/c++/4.9.2/functional:1329:53: error: invalid initialization of reference of type ‘int&&’ from expression of type ‘const int’
          typename add_const<_Functor>::type>::type>()(
                                                     ^
/usr/include/c++/4.9.2/functional:1347:2: note: template<class ... _Args, class _Result> _Result std::_Bind<_Functor(_Bound_args ...)>::operator()(_Args&& ...) volatile [with _Args = {_Args ...}; _Result = _Result; _Functor = void (*)(int&&); _Bound_args = {int}]
  operator()(_Args&&... __args) volatile
  ^
/usr/include/c++/4.9.2/functional:1347:2: note:   template argument deduction/substitution failed:
/usr/include/c++/4.9.2/functional:1343:70: error: invalid initialization of reference of type ‘int&&’ from expression of type ‘volatile int’
                        typename add_volatile<_Functor>::type>::type>()(
                                                                      ^
/usr/include/c++/4.9.2/functional:1361:2: note: template<class ... _Args, class _Result> _Result std::_Bind<_Functor(_Bound_args ...)>::operator()(_Args&& ...) const volatile [with _Args = {_Args ...}; _Result = _Result; _Functor = void (*)(int&&); _Bound_args = {int}]
  operator()(_Args&&... __args) const volatile
  ^
/usr/include/c++/4.9.2/functional:1361:2: note:   template argument deduction/substitution failed:
/usr/include/c++/4.9.2/functional:1357:64: error: invalid initialization of reference of type ‘int&&’ from expression of type ‘const volatile int’
                        typename add_cv<_Functor>::type>::type>()(

I tried looking for inspiration in other places, but Intel's TBB library which has a similar code (concurent_queue) with similar functionality (there's an emplace function) is actually no emplace at all - it constructs the object instantly and just "moves" it into the queue...

Any idea what's wrong with the code above? I suppose it's something really small, but I just cannot solve that myself...


(*) - https://github.com/DISTORTEC/distortos

Freddie Chopin
  • 8,440
  • 2
  • 28
  • 58
  • 2
    `std::bind` always [passes plain bound arguments as lvalues](http://stackoverflow.com/questions/26315604/stdbind-bind-lambda-with-rvalue-reference-as-argument/26315984#26315984), and lvalues cannot bind to an rvalue reference. – T.C. Dec 06 '14 at 10:05
  • Use a lambda for this kind of thing. – Puppy Dec 06 '14 at 10:11
  • @Puppy - could you explain? Do you mean I should use lambda for the whole functionality or maybe a lambda to create a functor with the bounded arguments instead of using `std::bind()`? – Freddie Chopin Dec 06 '14 at 10:14

2 Answers2

2

You've already had an explanation of how that is just how std::bind works (it turns everything into an lvalue), and to use a lambda instead. However, that is not exactly trivial. Lambdas can capture by value, or by reference. You sort of need a mix of both: rvalue references should be assumed to possibly reference temporaries, so should be captured by value, with move semantics. (Note: that does mean that the original object gets moved from before the lambda gets invoked.) Lvalue references should be captured by reference, for probably obvious reasons.

One way to make this work is to manually put the captured arguments in a tuple of lvalue reference types and non-reference types, and unpack when you want to invoke the function:

template <typename T>
struct remove_rvalue_reference {
  typedef T type;
};

template <typename T>
struct remove_rvalue_reference<T &&> {
  typedef T type;
};

template <typename T>
using remove_rvalue_reference_t = typename remove_rvalue_reference<T>::type;

template <typename F, typename...T, std::size_t...I>
decltype(auto) invoke_helper(F&&f, std::tuple<T...>&&t,
                             std::index_sequence<I...>) {
  return std::forward<F>(f)(std::get<I>(std::move(t))...);
}

template <typename F, typename...T>
decltype(auto) invoke(F&&f, std::tuple<T...>&&t) {
  return invoke_helper<F, T...>(std::forward<F>(f), std::move(t),
                                std::make_index_sequence<sizeof...(T)>());
}

template<typename T, typename... Args>
void emplacer(Args&&... args) {
  T{std::forward<Args>(args)...};
}

template<typename T, typename...Args>
void emplace(Args&&...args)
{
    auto boundFunction =
      [args=std::tuple<remove_rvalue_reference_t<Args>...>{
          std::forward<Args>(args)...}]() mutable {
        invoke(emplacer<T, Args...>, std::move(args));
      };
    boundFunction();
}

When calling emplace with args T1 &, T2 &&, the args will be captured in a tuple<T1 &, T2>. The tuple gets unpacked (thanks to @Johannes Schaub - litb for the basic idea) when finally invoking the function.

The lambda needs to be mutable, to allow that captured tuple to be moved from when invoking the function.

This uses several C++14 features. Most of these can be avoided, but I don't see how to do this without the ability to specify an initialiser in the capture list: C++11 lambdas can only capture by reference (which would be reference to the local variable), or by value (which would make a copy). In C++11, I think that means the only way to do it is not use a lambda, but effectively re-create most of std::bind.

Community
  • 1
  • 1
  • Worth noting that it's an init-capture that requires C++14. – T.C. Dec 06 '14 at 13:20
  • @T.C. Thanks, added a note. There's more that requires C++14 (`index_sequence`, `decltype(auto)`). Most of the rest can be rewritten to avoid that, but I think the initialiser is pretty much required. –  Dec 06 '14 at 13:26
  • @T.C. - not only that is C++14 in the code above (; `decltype(auto)` return type and `std::make_index_sequence` are too... N.B. the lack of `std::make_index_sequence` is why I'm using `std::bind()` instead of storing everything in a tuple - I use `std::bind` for my Thread objects too... – Freddie Chopin Dec 06 '14 at 13:26
  • 1
    @FreddieChopin FWIW, `decltype(auto)` here can be avoided by repeating the return expression (`decltype(auto) f() { return X; }` can become `auto f() -> decltype(X) { return X; }`), and `make_index_sequence` is purely standard library, not a language extension, and is implementable for C++11 too. –  Dec 06 '14 at 13:28
  • Yes, I know - I just try to stick to what is available. I think my use-case could use your solution with some simplification (I could create a tuple on stack and capture that by ref in the lambda), because - as I said in another comment - I don't need to pass this functor "up" to the caller, only "down", to type-independent non-template function, from where it is called using a virtual function (the functor is type-erased). But this also needs gcc 4.9 - earlier versions don't expand parameter packs in lambdas. – Freddie Chopin Dec 06 '14 at 13:36
  • 1
    @FreddieChopin I missed that comment. In that case, it's *far* simpler: you can simply capture *everything* by reference, and don't even bother with a `tuple` in the first place: something like `auto boundFunction = [&]() { emplacer(std::forward(args)...); };` should suffice for that. (For GCC 4.9, as you noted. You might need something else, possibly a `tuple`, for older versions.) –  Dec 06 '14 at 13:42
  • This still leaves the problem of GCC 4.9 (; I guess I'll have to wait for the new release of toolchain (at the end of this month, so soon enough) to use pack expansion in the lambda. My small test (on PC, with GCC 4.9) shows that capture by reference seems to work fine, with all move semantics working as expected, but a confirmation of my suspicion is much appreciated! – Freddie Chopin Dec 06 '14 at 13:52
  • @FreddieChopin I don't see any reason why it wouldn't work, at least. Each argument in `args` is a reference parameter and is captured by reference, and the use of `std::forward` causes it to be passed as an lvalue reference, or as an rvalue reference, as appropriate. –  Dec 06 '14 at 14:01
  • @hvd Hello again (; The solution with lambda is working very nice, but I'm still thinking about a different solution for GCC versions below 4.9. I've added std::apply(), std::invoke() and std::integer_sequence to my project and I'm wondering whether its possible to "dereference" the arguments from the tuple of references? std::apply() doesn't like the tuple of references when the function expects a rvalue reference and I'd prefer not to create a copy of such arguments. In other words - is it possible to implement the solution from one of the comments with std::tuple and std::apply()? – Freddie Chopin Jul 13 '15 at 21:23
1

To expand on @T.C.'s comment, you can make the code work by changing the type of the created emplacer.

auto boundFunction = std::bind(emplacer<T, Args&...>,
        std::forward<Args>(args)...);

Notice the & right after Args. The reason is you're passing an rvalue to the emplace function which in turn creates emplacer(int&&). std::bind however always passes an lvalue (because it comes from its internals). With the change in place, the signature changes to emplacer(int&) (after reference collapsing) which can bind to an lvalue.

Jiří Pospíšil
  • 14,296
  • 2
  • 41
  • 52
  • Indeed - this helps and the code compiles (; . I have a question though - is this a "generic" solution, or will that make other cases (not present in the example above) fail with an "opposite" error (or sth like that)? – Freddie Chopin Dec 06 '14 at 10:13
  • @FreddieChopin I've added more details to the answer. This kind of behavior is however a "limitation" of `std::bind` one needs to always keep in mind. – Jiří Pospíšil Dec 06 '14 at 10:23
  • Thx for the explanation. From my quick tests it seems that this solution has a serious limitation - you cannot actually use move-construction, because every moveable argument will always be converted into a const reference. I'm experimenting with a solution hinted (but sadly not explained further) by Puppy - using a lambda instead of `std::bind()` and it seems to handle that case a bit better, but the problem is that I need the result of `std::bind()` stored in an object, which I cannot do with lambda... Do you maybe have an opinion (or alternate solutions) on that? – Freddie Chopin Dec 06 '14 at 11:25
  • @FreddieChopin I'm not sure I understand the whole picture so I might a bit off, but I can imagine the solution with lambda might require move capture and you cannot do that easily in C++11 (http://stackoverflow.com/a/20669290/19093). – Jiří Pospíšil Dec 06 '14 at 13:05
  • I don't need to return this bound functor "up" in the call chain - it is only passed "down" and than called from type-independent function (using virtual function call). Using capture-by-reference seems to work, but only on PC - for ARM my compiler is gcc 4.8 which doesn't allow expanding of `args...` in the lambda (this needs 4.9). Now I thought about trying to use `std::bind()` with `std::ref()`, but this doesn't seem to work... – Freddie Chopin Dec 06 '14 at 13:21