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
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.
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.
Related
Here's a sample code: http://coliru.stacked-crooked.com/a/5f630d2d65cd983e
#include <variant>
#include <functional>
template<class... Ts> struct overloads : Ts... { using Ts::operator()...; };
template<class... Ts> overloads(Ts &&...) -> overloads<std::remove_cvref_t<Ts>...>;
template<typename... Ts, typename... Fs>
constexpr inline auto transform(const std::variant<Ts...> &var, Fs &&... fs)
-> decltype(auto) { return std::visit(overloads{fs...}, var); }
template<typename... Ts, typename... Fs>
constexpr inline auto transform_by_ref(const std::variant<Ts...> &var, Fs &&... fs)
-> decltype(auto) { return std::visit(overloads{std::ref(fs)...}, var); }
int main()
{
transform(
std::variant<int, double>{1.0},
[](int) { return 1; },
[](double) { return 2; }); // fine
transform_by_ref(
std::variant<int, double>{1.0},
[](int) { return 1; },
[](double) { return 2; }); // compilation error
return 0;
}
Here, I have adopted the well-known overloads helper type to invoke std::visit() with multiple lambdas.
transform() copies function objects so I write a new function transform_by_ref() which utilizes std::reference_wrapper to prevent copying function objects.
Even though original lambdas are temporary objects, the lifetime is ensured at the end of execution of transform_by_ref() and I think lifetime should not be a problem here.
transform() works as expected but transform_by_ref() causes compilation error:
main.cpp: In instantiation of 'constexpr decltype(auto) transform_by_ref(const std::variant<_Types ...>&, Fs&& ...) [with Ts = {int, double}; Fs = {main()::<lambda(int)>, main()::<lambda(double)>}]':
main.cpp:18:21: required from here
main.cpp:13:42: error: no matching function for call to 'visit(overloads<std::reference_wrapper<main()::<lambda(int)> >, std::reference_wrapper<main()::<lambda(double)> > >, const std::variant<int, double>&)'
13 | -> decltype(auto) { return std::visit(overloads{std::ref(fs)...}, var); }
| ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from main.cpp:1:
/usr/local/include/c++/12.1.0/variant:1819:5: note: candidate: 'template<class _Visitor, class ... _Variants> constexpr std::__detail::__variant::__visit_result_t<_Visitor, _Variants ...> std::visit(_Visitor&&, _Variants&& ...)'
1819 | visit(_Visitor&& __visitor, _Variants&&... __variants)
| ^~~~~
/usr/local/include/c++/12.1.0/variant:1819:5: note: template argument deduction/substitution failed:
In file included from /usr/local/include/c++/12.1.0/variant:37:
/usr/local/include/c++/12.1.0/type_traits: In substitution of 'template<class _Fn, class ... _Args> using invoke_result_t = typename std::invoke_result::type [with _Fn = overloads<std::reference_wrapper<main()::<lambda(int)> >, std::reference_wrapper<main()::<lambda(double)> > >; _Args = {const int&}]':
/usr/local/include/c++/12.1.0/variant:1093:11: required by substitution of 'template<class _Visitor, class ... _Variants> using __visit_result_t = std::invoke_result_t<_Visitor, std::__detail::__variant::__get_t<0, _Variants, decltype (std::__detail::__variant::__as(declval<_Variants>())), typename std::variant_alternative<0, typename std::remove_reference<decltype (std::__detail::__variant::__as(declval<_Variants>()))>::type>::type>...> [with _Visitor = overloads<std::reference_wrapper<main()::<lambda(int)> >, std::reference_wrapper<main()::<lambda(double)> > >; _Variants = {const std::variant<int, double>&}]'
/usr/local/include/c++/12.1.0/variant:1819:5: required by substitution of 'template<class _Visitor, class ... _Variants> constexpr std::__detail::__variant::__visit_result_t<_Visitor, _Variants ...> std::visit(_Visitor&&, _Variants&& ...) [with _Visitor = overloads<std::reference_wrapper<main()::<lambda(int)> >, std::reference_wrapper<main()::<lambda(double)> > >; _Variants = {const std::variant<int, double>&}]'
main.cpp:13:42: required from 'constexpr decltype(auto) transform_by_ref(const std::variant<_Types ...>&, Fs&& ...) [with Ts = {int, double}; Fs = {main()::<lambda(int)>, main()::<lambda(double)>}]'
main.cpp:18:21: required from here
/usr/local/include/c++/12.1.0/type_traits:3034:11: error: no type named 'type' in 'struct std::invoke_result<overloads<std::reference_wrapper<main()::<lambda(int)> >, std::reference_wrapper<main()::<lambda(double)> > >, const int&>'
3034 | using invoke_result_t = typename invoke_result<_Fn, _Args...>::type;
| ^~~~~~~~~~~~~~~
main.cpp: In instantiation of 'constexpr decltype(auto) transform_by_ref(const std::variant<_Types ...>&, Fs&& ...) [with Ts = {int, double}; Fs = {main()::<lambda(int)>, main()::<lambda(double)>}]':
main.cpp:18:21: required from here
/usr/local/include/c++/12.1.0/variant:1859:5: note: candidate: 'template<class _Res, class _Visitor, class ... _Variants> constexpr _Res std::visit(_Visitor&&, _Variants&& ...)'
1859 | visit(_Visitor&& __visitor, _Variants&&... __variants)
| ^~~~~
/usr/local/include/c++/12.1.0/variant:1859:5: note: template argument deduction/substitution failed:
main.cpp:13:42: note: couldn't deduce template parameter '_Res'
13 | -> decltype(auto) { return std::visit(overloads{std::ref(fs)...}, var); }
| ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
I think I can fix this by not using std::visit() and implement my own visit function anyway.
However, I want to know why this code does not work as expected.
Why does my transform_by_ref() cause compilation error and how to fix it without custom visit function implementation?
Each std::reference_wrapper has an operator() overload that can be called with any argument list that the referenced lambda would accept as arguments.
That means the reference wrappers for both [](int) { return 1; } and [](double) { return 2; } have operator() overloads that accept an int argument as well as an double argument, both without conversion of the argument.
So when std::visit tries to do overload resolution for a specific element type of the variant, the operator() overloads made visible via using Ts::operator()...; for both reference wrappers of the lambdas will be viable, but in contrast to the non-reference-wrapper case, both overloads will be viable without conversions of the argument, meaning that they are equally good and hence overload resolution ambiguous.
The ambiguity can be avoided by enforcing that the lambdas take only exactly the type they are supposed to match as argument (assuming C++20 here):
transform_by_ref(
std::variant<int, double>{1.0},
[](std::same_as<int> auto) { return 1; },
[](std::same_as<double> auto) { return 2; });
or by using a single overload with if constexpr in its body to branch on the type of the argument.
While it is possible to make the operator() of a wrapper class SFINAE-friendly so that it won't be considered viable if the wrapped callable isn't, it is impossible to "forward" the conversion rank of calls for calls to such a wrapper, at least in general. For non-generic lambdas specifically, it is theoretically possible to extract the parameter type in the wrapper and use it as the parameter type of the operator() overload, but that is messy and doesn't work with generic callables. Proper reflection would be required to implement such a wrapper.
In your code for transform you are using fs directly as lvalue instead of properly forwarding its value category via std::forward<Fs>(fs). If you used that instead, then only move construction would be used, instead of copies.
If the goal is to also avoid the move construction, the usual approach which constructs overloads in the caller already achieves that:
template<typename... Ts, typename Fs>
constexpr inline auto transform(const std::variant<Ts...> &var, Fs && fs)
-> decltype(auto) { return std::visit(std::forward<Fs>(fs), var); }
int main()
{
transform(
std::variant<int, double>{1.0},
overloads{
[](int) { return 1; },
[](double) { return 2; }});
return 0;
}
This uses aggregate-initialization of overloads from prvalues, which means mandatory copy elision applies and no lambdas will be copied or moved.
The std::ref approach, even if it did work, would also waste memory to store the references for non-capturing lambdas.
While working on a project I encounter a situation std::apply does not forward rvalue references from std::tuple created by std::forward_as_tuple *IF* resulting std::tuple is stored in a variable! However if std::forward_as_tuple result is not stored in a variable, but is just passed as a second argument to std::apply then it works and rvalue reference gets perfectly forwarded.
I tried many options including using different types for std::tuple, like
decltype(auto) t = forward_as_tuple(1, std::move(r))
auto t = forward_as_tuple(1, std::move(r))
auto&& t = forward_as_tuple(1, std::move(r))
auto& t = forward_as_tuple(1, std::move(r))
Nothing helped to store a tuple in a variable and then pass it to std::apply. It appears like lvalue reference being forwarded at the end to std::__invoke by std::apply...
There is a godbolt link to my code: https://godbolt.org/z/24LYP5
Code snippet
#include <functional>
#include <iostream>
auto product(int l, int&& r) { return l * r; }
static void test_not_works()
{
int r = 2;
decltype(auto) t = std::forward_as_tuple(1, std::move(r));
std::apply(product, t);
}
static void test_works()
{
int r = 2;
std::apply(product, std::forward_as_tuple(1, std::move(r)));
}
Error message
In file included from /opt/compiler-explorer/gcc-7.3.0/include/c++/7.3.0/functional:54:0,
from <source>:1:
/opt/compiler-explorer/gcc-7.3.0/include/c++/7.3.0/tuple: In instantiation of 'constexpr decltype(auto) std::__apply_impl(_Fn&&, _Tuple&&, std::index_sequence<_Idx ...>) [with _Fn = int (&)(int, int&&); _Tuple = std::tuple<int&&, int&&>&; long unsigned int ..._Idx = {0, 1}; std::index_sequence<_Idx ...> = std::integer_sequence<long unsigned int, 0, 1>]':
/opt/compiler-explorer/gcc-7.3.0/include/c++/7.3.0/tuple:1671:31: required from 'constexpr decltype(auto) std::apply(_Fn&&, _Tuple&&) [with _Fn = int (&)(int, int&&); _Tuple = std::tuple<int&&, int&&>&]'
<source>:10:26: required from here
/opt/compiler-explorer/gcc-7.3.0/include/c++/7.3.0/tuple:1662:27: error: no matching function for call to '__invoke(int (&)(int, int&&), int&, int&)'
return std::__invoke(std::forward<_Fn>(__f),
~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~
std::get<_Idx>(std::forward<_Tuple>(__t))...);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /opt/compiler-explorer/gcc-7.3.0/include/c++/7.3.0/tuple:41:0,
from /opt/compiler-explorer/gcc-7.3.0/include/c++/7.3.0/functional:54,
from <source>:1:
/opt/compiler-explorer/gcc-7.3.0/include/c++/7.3.0/bits/invoke.h:89:5: note: candidate: template<class _Callable, class ... _Args> constexpr typename std::__invoke_result<_Functor, _ArgTypes>::type std::__invoke(_Callable&&, _Args&& ...)
__invoke(_Callable&& __fn, _Args&&... __args)
^~~~~~~~
/opt/compiler-explorer/gcc-7.3.0/include/c++/7.3.0/bits/invoke.h:89:5: note: template argument deduction/substitution failed:
/opt/compiler-explorer/gcc-7.3.0/include/c++/7.3.0/bits/invoke.h: In substitution of 'template<class _Callable, class ... _Args> constexpr typename std::__invoke_result<_Functor, _ArgTypes>::type std::__invoke(_Callable&&, _Args&& ...) [with _Callable = int (&)(int, int&&); _Args = {int&, int&}]':
/opt/compiler-explorer/gcc-7.3.0/include/c++/7.3.0/tuple:1662:27: required from 'constexpr decltype(auto) std::__apply_impl(_Fn&&, _Tuple&&, std::index_sequence<_Idx ...>) [with _Fn = int (&)(int, int&&); _Tuple = std::tuple<int&&, int&&>&; long unsigned int ..._Idx = {0, 1}; std::index_sequence<_Idx ...> = std::integer_sequence<long unsigned int, 0, 1>]'
/opt/compiler-explorer/gcc-7.3.0/include/c++/7.3.0/tuple:1671:31: required from 'constexpr decltype(auto) std::apply(_Fn&&, _Tuple&&) [with _Fn = int (&)(int, int&&); _Tuple = std::tuple<int&&, int&&>&]'
<source>:10:26: required from here
/opt/compiler-explorer/gcc-7.3.0/include/c++/7.3.0/bits/invoke.h:89:5: error: no type named 'type' in 'struct std::__invoke_result<int (&)(int, int&&), int&, int&>'
Compiler returned: 1
When rvalue references are passed to std::forward_as_tuple, it constructs a std::tuple of rvalue references. So first of all, declaring t as auto t = std::forward_as_tuple(...) is fine, the "rvalueness" of the object is encoded inside the type generated by std::forward_as_tuple.
But then note that there is something special about the first argument to std::apply: product takes an int parameter by value, and a second one as int&&, i.e., an rvalue-reference. Calling such a function obviously requires the second argument to be an rvalue reference, but this does only work if you make sure it is one. Hence:
auto t = std::forward_as_tuple(1, std::move(r));
std::apply(product, std::move(t));
// ^^^^^^^ cast to rvalue here
Another possible fix is to change product to accept to plain ints by value (in case of an int, there is no performance hit anyway). Then, you can pass t as an lvalue, too.
std::apply perfect-forwards the tuple to std::get to access elements of the tuple. std::get returns an lvalue reference if its tuple argument is an lvalue. Since your t variable is an lvalue, std::apply calls product with an int& instead of an int&&.
I can't speak for the motivation behind having it work this way despite the tuple explicitly holding an rvalue reference. However, a simple way to get the same behaviour as a temporary here is to use std::move to produce an xvalue, which will handle stored references in the way you want:
std::apply(product, std::move(t));
Note, however, that unlike using just t, any non-reference types in the tuple should now be treated as moved from. I'm unsure if there's a simple way to both use the tuple's reference types and treat non-reference types as lvalues for an lvalue tuple. Granted this won't be an issue when creating the tuple using forward_as_tuple since it will always contain references.
I'm trying to implement a class which wraps an arbitrary type and a mutex. To access the wrapped data, one needs to pass a function object as parameter of the locked method. The wrapper class will then pass the wrapped data as parameter to this function object.
I'd like my wrapper class to work with const & non-const, so I tried the following
#include <mutex>
#include <string>
template<typename T, typename Mutex = std::mutex>
class Mutexed
{
private:
T m_data;
mutable Mutex m_mutex;
public:
using type = T;
using mutex_type = Mutex;
public:
explicit Mutexed() = default;
template<typename... Args>
explicit Mutexed(Args&&... args)
: m_data{std::forward<Args>(args)...}
{}
template<typename F>
auto locked(F&& f) -> decltype(std::forward<F>(f)(m_data)) {
std::lock_guard<Mutex> lock(m_mutex);
return std::forward<F>(f)(m_data);
}
template<typename F>
auto locked(F&& f) const -> decltype(std::forward<F>(f)(m_data)) {
std::lock_guard<Mutex> lock(m_mutex);
return std::forward<F>(f)(m_data);
}
};
int main()
{
Mutexed<std::string> str{"Foo"};
str.locked([](auto &s) { /* this doesn't compile */
s = "Bar";
});
str.locked([](std::string& s) { /* this compiles fine */
s = "Baz";
});
return 0;
}
The first locked call with the generic lambda fails to compile with the following error
/home/foo/tests/lamdba_auto_const/lambda_auto_const/main.cpp: In instantiation of ‘main()::<lambda(auto:1&)> [with auto:1 = const std::__cxx11::basic_string<char>]’:
/home/foo/tests/lamdba_auto_const/lambda_auto_const/main.cpp:30:60: required by substitution of ‘template<class F> decltype (forward<F>(f)(((const Mutexed<T, Mutex>*)this)->Mutexed<T, Mutex>::m_data)) Mutexed<T, Mutex>::locked(F&&) const [with F = main()::<lambda(auto:1&)>]’
/home/foo/tests/lamdba_auto_const/lambda_auto_const/main.cpp:42:6: required from here
/home/foo/tests/lamdba_auto_const/lambda_auto_const/main.cpp:41:11: error: passing ‘const std::__cxx11::basic_string<char>’ as ‘this’ argument discards qualifiers [-fpermissive]
s = "Bar";
^
In file included from /usr/include/c++/5/string:52:0,
from /usr/include/c++/5/stdexcept:39,
from /usr/include/c++/5/array:38,
from /usr/include/c++/5/tuple:39,
from /usr/include/c++/5/mutex:38,
from /home/foo/tests/lamdba_auto_const/lambda_auto_const/main.cpp:1:
/usr/include/c++/5/bits/basic_string.h:558:7: note: in call to ‘std::__cxx11::basic_string<_CharT, _Traits, _Alloc>& std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::operator=(const _CharT*) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’
operator=(const _CharT* __s)
^
But the second call with the std::string& parameter is fine.
Why is that ? And is there a way to make it work as expected while using a generic lambda ?
This is a problem fundamentally with what happens with SFINAE-unfriendly callables. For more reference, check out P0826.
The problem is, when you call this:
str.locked([](auto &s) { s = "Bar"; });
We have two overloads of locked and we have to try both. The non-const overload works fine. But the const one – even if it won't be selected by overload resolution anyway – still has to be instantiated (it's a generic lambda, so to figure out what decltype(std::forward<F>(f)(m_data)) might be you have to instantiate it) and that instantiation fails within the body of the lambda. The body is outside of the immediate context, so it's not a substitution failure – it's a hard error.
When you call this:
str.locked([](std::string& s) { s = "Bar"; });
We don't need to look at the body at all during the whole process of overload resolution – we can simply reject at the call site (since you can't pass a const string into a string&).
There's not really a solution to this problem in the language today – you basically have to add constraints on your lambda to ensure that the instantiation failure happens in the immediate context of substitution rather than in the body. Something like:
str.locked([](auto &s) -> void {
s = "Bar";
});
Note that we don't need to make this SFINAE-friendly - we just need to ensure that we can determine the return type without instantiating the body.
A more thorough language solution would have been to allow for "Deducing this" (see the section in the paper about this specific problem). But that won't be in C++20.
I am trying to write a wrapper around std::thread:
#include <thread>
#include <iostream>
struct A {};
template <typename F, typename... Args>
void lifted_lambda_1(void *m, F &&entrypoint, Args&&... args) {
std::cout << "I will do something with the void * " << m << std::endl;
entrypoint(std::forward<Args>(args)...);
}
template <typename F, typename... Args>
void make_thread(void *p, F &&f, Args && ... args) {
std::thread(lifted_lambda_1<typename std::decay<F>::type, Args...>, p, std::forward<F>(f), std::forward<Args>(args)...).detach();
}
int main() {
A a;
make_thread(nullptr, [](A x){}, a);
}
But when I compile it I get an error:
In file included from /usr/local/sqream-prerequisites/package-install/gcc-4.8.2/include/c++/4.8.2/thread:39:0,
from bubu.cpp:1:
/usr/local/sqream-prerequisites/package-install/gcc-4.8.2/include/c++/4.8.2/functional: In instantiation of ‘struct std::_Bind_simple<void (*(void*, main()::__lambda0, A))(void*, main()::__lambda0&&, A&)>’:
/usr/local/sqream-prerequisites/package-install/gcc-4.8.2/include/c++/4.8.2/thread:137:47: required from ‘std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (&)(void*, main()::__lambda0&&, A&); _Args = {void*&, main()::__lambda0, A&}]’
bubu.cpp:15:132: required from ‘void make_thread(void*, F&&, Args&& ...) [with F = main()::__lambda0; Args = {A&}]’
bubu.cpp:20:38: required from here
/usr/local/sqream-prerequisites/package-install/gcc-4.8.2/include/c++/4.8.2/functional:1697:61: error: no type named ‘type’ in ‘class std::result_of<void (*(void*, main()::__lambda0, A))(void*, main()::__lambda0&&, A&)>’
typedef typename result_of<_Callable(_Args...)>::type result_type;
^
/usr/local/sqream-prerequisites/package-install/gcc-4.8.2/include/c++/4.8.2/functional:1727:9: error: no type named ‘type’ in ‘class std::result_of<void (*(void*, main()::__lambda0, A))(void*, main()::__lambda0&&, A&)>’
_M_invoke(_Index_tuple<_Indices...>)
What is the reason for this error? How do I fix it?
std::thread will always decay its arguments (for the reasons given at the link in one of the comments above). You can use reference_wrapper to protect references, so that arguments can be passed by lvalue reference.
To make that work with both lvalue and rvalue arguments you need a wrapper function which will wrap lvalues in reference_wrapper but allow rvalues to be copied (or moved) and forwarded as rvalues. This will not be "perfect" forwarding, because rvalues will be copied, not forwarded as rvalue references, so the target function gets called with new objects.
So you can use something like this to conditionally wrap lvalues but just forward rvalues:
template<typename T>
std::reference_wrapper<std::remove_reference_t<T>>
wrap(T& t) { return std::ref(t); }
template<typename T>
T&&
wrap(typename std::remove_reference<T>::type&& t)
{ return std::move(t); }
(remove_reference is used on the second overload so that T is in a non-deduced context, and so that the argument is not a forwarding reference).
Then use that for the arguments to the thread constructor:
std::thread(lifted_lambda_1<typename std::decay<F>::type, Args...>, p,
std::forward<F>(f), wrap<Args>(args)...).detach();
/*^^^^^^^^^^^^*/
However, doing this brings back all the problems that std::thread tries to avoid by copying its arguments! You must ensure that any lvalues passed to make_thread will not go out of scope before the thread finishes running. Since you are detaching the thread, that is very difficult to do in general. You must be very careful when using this function.
Potentially you could write your own class template that behaves like reference_wrapper that protects rvalue references, to avoid the new objects being created, but then you must also be careful that the rvalue arguments to the thread function do not go out of scope before the thread finishes running (and if they are rvalues there is a high probability that they are temporaries which will not outlive the call that creates the new thread!)
Here be dragons.
I have this simple class:
struct Worker
{
Worker() : done{false} {}
Worker(const Worker& rhs) : done{rhs.done}, qworker{} {}
Worker(Worker &&rhs) : done{rhs.done}
{
qworker = std::move(rhs.qworker);
}
...
}
this compile fine with gcc-4.7.2 but if I try to use this version I obtain an error
struct Worker
{
Worker() : done{false} {}
Worker(const Worker& rhs) : done{rhs.done}, qworker{} {}
Worker(Worker &&rhs) : done{rhs.done}
, qworker{std::move(rhs.qworker)} // <- ERROR
{
}
...
}
Why?
In file included from tlog.cpp:8:0:
log11.hpp: In member function ‘void Log11::Worker::run()’:
log11.hpp:34:29: error: ‘class std::vector<std::function<void()> >’ has no member named ‘pop_front’
In file included from /usr/include/c++/4.7/thread:39:0,
from tlog.cpp:3:
/usr/include/c++/4.7/functional: In instantiation of ‘static void std::_Function_handler<void(_ArgTypes ...), _Functor>::_M_invoke(const std::_Any_data&, _ArgTypes ...) [with _Functor = std::vector<std::function<void()> >; _ArgTypes = {}]’:
/usr/include/c++/4.7/functional:2298:6: required from ‘std::function<_Res(_ArgTypes ...)>::function(_Functor, typename std::enable_if<(! std::is_integral<_Functor>::value), std::function<_Res(_ArgTypes ...)>::_Useless>::type) [with _Functor = std::vector<std::function<void()> >; _Res = void; _ArgTypes = {}; typename std::enable_if<(! std::is_integral<_Functor>::value), std::function<_Res(_ArgTypes ...)>::_Useless>::type = std::function<void()>::_Useless]’
log11.hpp:20:78: required from here
/usr/include/c++/4.7/functional:1926:2: error: no match for call to ‘(std::vector<std::function<void()> >) ()’
According to the C++11 standard std::function has an unconstrained constructor template that accepts any argument type:
template<class F> function(F f);
When you say qworker{std::move(rhs.qworker)} this first attempts to call a constructor taking std::initializer_list<std::function<void()>>. Because of the unconstrained constructor template shown above, a std::function<void()> can be constructed from any type, so you get an initializer_list with one member, like this:
{ std::function<void()>{std::move(rhs.qworker)} }
This is invalid, because rhs.qworker is not a callable object, but the error only happens when you try to invoke the function objects.
If you say qworker(std::move(rhs.qworker)) then the initializer list constructor is not a candidate and the move constructor is called instead.
There is a defect report against the standard (LWG 2132) which fixes this by preventing the function(F) constructor template being called unless the argument is a callable object. That prevents an initializer_list<function<void()>> being created, and instead qworker{std::move(rhs.qworker)} calls the move constructor, as intended. GCC 4.7 does not implement the resolution for LWG 2132, but GCC 4.8 does.