Capturing perfectly-forwarded variable in lambda - c++

template<typename T> void doSomething(T&& mStuff)
{
auto lambda([&mStuff]{ doStuff(std::forward<T>(mStuff)); });
lambda();
}
Is it correct to capture the perfectly-forwarded mStuff variable with the &mStuff syntax?
Or is there a specific capture syntax for perfectly-forwarded variables?
EDIT: What if the perfectly-forwarded variable is a parameter pack?

Is it correct to capture the perfectly-forwarded mStuff variable with
the &mStuff syntax?
Yes, assuming that you don't use this lambda outside doSomething. Your code captures mStuff per reference and will correctly forward it inside the lambda.
For mStuff being a parameter pack it suffices to use a simple-capture with a pack-expansion:
template <typename... T> void doSomething(T&&... mStuff)
{
auto lambda = [&mStuff...]{ doStuff(std::forward<T>(mStuff)...); };
}
The lambda captures every element of mStuff per reference. The closure-object saves an lvalue reference for to each argument, regardless of its value category. Perfect forwarding still works; In fact, there isn't even a difference because named rvalue references would be lvalues anyway.

To make the lambda valid outside the scope where it's created, you need a wrapper class that handles lvalues and rvalues differently, i.e., keeps a reference to an lvalue, but makes a copy of (by moving) an rvalue.
Header file capture.h:
#pragma once
#include <type_traits>
#include <utility>
template < typename T >
class capture_wrapper
{
static_assert(not std::is_rvalue_reference<T>{},"");
std::remove_const_t<T> mutable val_;
public:
constexpr explicit capture_wrapper(T&& v)
noexcept(std::is_nothrow_move_constructible<std::remove_const_t<T>>{})
:val_(std::move(v)){}
constexpr T&& get() const noexcept { return std::move(val_); }
};
template < typename T >
class capture_wrapper<T&>
{
T& ref_;
public:
constexpr explicit capture_wrapper(T& r) noexcept : ref_(r){}
constexpr T& get() const noexcept { return ref_; }
};
template < typename T >
constexpr typename std::enable_if<
std::is_lvalue_reference<T>{},
capture_wrapper<T>
>::type
capture(std::remove_reference_t<T>& t) noexcept
{
return capture_wrapper<T>(t);
}
template < typename T >
constexpr typename std::enable_if<
std::is_rvalue_reference<T&&>{},
capture_wrapper<std::remove_reference_t<T>>
>::type
capture(std::remove_reference_t<T>&& t)
noexcept(std::is_nothrow_constructible<capture_wrapper<std::remove_reference_t<T>>,T&&>{})
{
return capture_wrapper<std::remove_reference_t<T>>(std::move(t));
}
template < typename T >
constexpr typename std::enable_if<
std::is_rvalue_reference<T&&>{},
capture_wrapper<std::remove_reference_t<T>>
>::type
capture(std::remove_reference_t<T>& t)
noexcept(std::is_nothrow_constructible<capture_wrapper<std::remove_reference_t<T>>,T&&>{})
{
return capture_wrapper<std::remove_reference_t<T>>(std::move(t));
}
Example/test code that shows it works. Note that the "bar" example shows how one can use std::tuple<...> to work around the lack of pack expansion in lambda capture initializer, useful for variadic capture.
#include <cassert>
#include <tuple>
#include "capture.h"
template < typename T >
auto foo(T&& t)
{
return [t = capture<T>(t)]()->decltype(auto)
{
auto&& x = t.get();
return std::forward<decltype(x)>(x);
// or simply, return t.get();
};
}
template < std::size_t... I, typename... T >
auto bar_impl(std::index_sequence<I...>, T&&... t)
{
static_assert(std::is_same<std::index_sequence<I...>,std::index_sequence_for<T...>>{},"");
return [t = std::make_tuple(capture<T>(t)...)]()
{
return std::forward_as_tuple(std::get<I>(t).get()...);
};
}
template < typename... T >
auto bar(T&&... t)
{
return bar_impl(std::index_sequence_for<T...>{}, std::forward<T>(t)...);
}
int main()
{
static_assert(std::is_same<decltype(foo(0)()),int&&>{}, "");
assert(foo(0)() == 0);
auto i = 0;
static_assert(std::is_same<decltype(foo(i)()),int&>{}, "");
assert(&foo(i)() == &i);
const auto j = 0;
static_assert(std::is_same<decltype(foo(j)()),const int&>{}, "");
assert(&foo(j)() == &j);
const auto&& k = 0;
static_assert(std::is_same<decltype(foo(std::move(k))()),const int&&>{}, "");
assert(foo(std::move(k))() == k);
auto t = bar(0,i,j,std::move(k))();
static_assert(std::is_same<decltype(t),std::tuple<int&&,int&,const int&,const int&&>>{}, "");
assert(std::get<0>(t) == 0);
assert(&std::get<1>(t) == &i);
assert(&std::get<2>(t) == &j);
assert(std::get<3>(t) == k and &std::get<3>(t) != &k);
}

TTBOMK, for C++14, I think the above solutions for lifetime handling can be simplified to:
template <typename T> capture { T value; }
template <typename T>
auto capture_example(T&& value) {
capture<T> cap{std::forward<T>(value)};
return [cap = std::move(cap)]() { /* use cap.value *; };
};
or more anonymous:
template <typename T>
auto capture_example(T&& value) {
struct { T value; } cap{std::forward<T>(value)};
return [cap = std::move(cap)]() { /* use cap.value *; };
};
Used it here (admittedly, this particular block of code is rather useless :P)
https://github.com/EricCousineau-TRI/repro/blob/3fda1e0/cpp/generator.cc#L161-L176

Yes you can do perfect capturing, but not directly. You will need to wrap the type in another class:
#define REQUIRES(...) class=std::enable_if_t<(__VA_ARGS__)>
template<class T>
struct wrapper
{
T value;
template<class X, REQUIRES(std::is_convertible<T, X>())>
wrapper(X&& x) : value(std::forward<X>(x))
{}
T get() const
{
return std::move(value);
}
};
template<class T>
auto make_wrapper(T&& x)
{
return wrapper<T>(std::forward<T>(x));
}
Then pass them as parameters to a lambda that returns a nested lambda that captures the parameters by value:
template<class... Ts>
auto do_something(Ts&&... xs)
{
auto lambda = [](auto... ws)
{
return [=]()
{
// Use `.get()` to unwrap the value
some_other_function(ws.get()...);
};
}(make_wrapper(std::forward<Ts>(xs)...));
lambda();
}

Here's a solution for C++17 that uses deduction guides to make it easy. I'm elaborating on Vittorio Romeo's (the OP) blog post, where he provides a solution to his own question.
std::tuple can be used to wrap the perfectly forwarded variables, making a copy or keeping a reference of each of them on a per-variable basis, as needed. The tuple itself is value-captured by the lambda.
To make it easier and cleaner, I'm going to create a new type derived from std::tuple, so to provide guided construction (that will let us avoid the std::forward and decltype() boilerplate) and pointer-like accessors in case there's just one variable to capture.
// This is the generic case
template <typename... T>
struct forwarder: public std::tuple<T...> {
using std::tuple<T...>::tuple;
};
// This is the case when just one variable is being captured.
template <typename T>
struct forwarder<T>: public std::tuple<T> {
using std::tuple<T>::tuple;
// Pointer-like accessors
auto &operator *() {
return std::get<0>(*this);
}
const auto &operator *() const {
return std::get<0>(*this);
}
auto *operator ->() {
return &std::get<0>(*this);
}
const auto *operator ->() const {
return &std::get<0>(*this);
}
};
// std::tuple_size needs to be specialized for our type,
// so that std::apply can be used.
namespace std {
template <typename... T>
struct tuple_size<forwarder<T...>>: tuple_size<tuple<T...>> {};
}
// The below two functions declarations are used by the deduction guide
// to determine whether to copy or reference the variable
template <typename T>
T forwarder_type(const T&);
template <typename T>
T& forwarder_type(T&);
// Here comes the deduction guide
template <typename... T>
forwarder(T&&... t) -> forwarder<decltype(forwarder_type(std::forward<T>(t)))...>;
And then one can use it like following.
The variadic version:
// Increment each parameter by 1 at each invocation and print it.
// Rvalues will be copied, Lvalues will be passed as references.
auto variadic_incrementer = [](auto&&... a)
{
return [a = forwarder(a...)]() mutable
{
std::apply([](auto &&... args) {
(++args._value,...);
((std::cout << "variadic_incrementer: " << args._value << "\n"),...);
}, a);
};
};
The non-variadic version:
// Increment the parameter by 1 at each invocation and print it.
// Rvalues will be copied, Lvalues will be passed as references.
auto single_incrementer = [](auto&& a)
{
return [a = forwarder(a)]() mutable
{
++a->_value;
std::cout << "single_incrementer: " << a->_value << "\n";
};
};

Related

Generically wrap member function of object to modify return type

Version restriction: C++17.
I'm trying to create a type capable of accepting a callable object of any type and wrapping one of its member functions (in this case, operator()) to take the same arguments, but modify (cast) the return type. An example follows:
template <typename Ret, typename Callable>
struct ReturnConverter : Callable
{
ReturnConverter(Callable cb) : Callable(cb) { }
Ret operator()(argsof(Callable::operator()) args) // magic happens here
{
return static_cast<Ret>(Callable::operator()(std::forward<??>(args)); // how to forward?
}
};
template <typename Ret, typename Callable>
auto make_converter(Callable cb)
{
return ReturnConverter<Ret, Callable>(cb);
}
int main()
{
auto callable = []() { return 1.0f; };
auto converted = make_converter<int>(callable);
auto x = converted(); // decltype(x) = int
}
ReturnConverter can take an object and override that object's operator() to cast whatever it returns to Ret.
The problem is expressing the argument types of the wrapping function - they should be exactly the same as those of Callable::operator(). Using a variadic template with std::forward does not satisfy this goal, as it would modify the signature of the function (operator() now becomes a template where it wasn't before).
How can I express the argsof operator I've highlighted above?
Motivation: I'd like to modify the std::visit overload technique demonstrated in this article to be able to specify the desired return type from multiple lambda functors, so that I don't have to strictly match the return type in every lambda, for instance:
std::variant<int, float, void*> v = ...;
auto stringify = overload(
[](int x) { return "int: " + std::to_string(x); },
[](float x) { return "float: " + std::to_string(x); },
[](auto v) { return "invalid type!"; } // error! const char* != std::string
);
std::visit(stringify, v);
With the change above, I'd be able to write something like auto stringify = overload<std::string>(...);
I don't see a way to respond to your exact answer but... considering the "Motivation" of the question... I propose a wrapper for overload (a class that inherit from a class with one or more operator(), call the appropriate operator() from the base class and cast the return value to type Ret)
template <typename Ret, typename Wrpd>
struct wrp_overload : public Wrpd
{
template <typename ... Args>
Ret operator() (Args && ... as)
{ return Wrpd::operator()(std::forward<Args...>(as)...); }
};
and, given the Ret type isn't deducible from the argument (the overload class) and that CTAD doesn't permit to explicit a template argumend, seems to me that a make_wrp_overload() function is required
template <typename Ret, typename ... Cs>
auto make_wrp_overload (Cs && ... cs)
{ return wrp_overload<Ret, overload<Cs...>>{{std::forward<Cs>(cs)...}}; }
so your std::visit() call become
std::visit(make_wrp_overload<std::string>(
[](int x) { return "int: " + std::to_string(x); },
[](float x) { return "float: " + std::to_string(x); },
[](auto v) { return "invalid type!"; }
), package);
The following is a full compiling C++17 example
#include <iostream>
#include <variant>
template <typename ... Ts>
struct overload : public Ts...
{ using Ts::operator()...; };
// not required anymore (also C++17)
//template <typename ... Ts> overload(Ts...) -> overload<Ts...>;
template <typename Ret, typename Wrpd>
struct wrp_overload : public Wrpd
{
template <typename ... Args>
Ret operator() (Args && ... as)
{ return Wrpd::operator()(std::forward<Args...>(as)...); }
};
template <typename Ret, typename ... Cs>
auto make_wrp_overload (Cs && ... cs)
{ return wrp_overload<Ret, overload<Cs...>>{{std::forward<Cs>(cs)...}}; }
int main() {
std::variant<int, float, void*> package;
std::visit(make_wrp_overload<std::string>(
[](int x) { return "int: " + std::to_string(x); },
[](float x) { return "float: " + std::to_string(x); },
[](auto v) { return "(no more) invalid type"; }
), package);
}

Can a lambda be passed a as a template parameter in C++17?

I've read multiple answers on SO with lambdas being passed to class templates but for some reason I cant achieve it... I am using g++ version 9 with C++17 being used.
#include <string>
struct Type {
Type();
Type(int);
int theVal();
};
template<typename Key, typename Value, Key(*KeyFunc)(Type t) = nullptr>
struct MyClass {
MyClass(){}
~MyClass(){}
void add(const Key key, const Value value){
//do stuff
}
void add(const Value value){
this->add(KeyFunc(value), value);
}
};
int main(){
MyClass<
int,
std::string,
+[](Type t){
return t.theVal();
}
> instance;
Type value(100);
instance.add(value);
return 0;
}
The error message tells me I cant have a lambda in a template.
Yes, but it need to first declared outside the template parameter, and the lambda must be captureless:
auto lambda = [](Type t) {
return t.theVal();
};
// Works, C++17 allows constexpr conversion for nttp
MyClass<int, Type, lambda> instance;
In C++20, you can use C++17's auto template parameter and a lambda directly in the template parameters:
constexpr auto noop = [](auto&& v) -> decltype(auto) {
return static_cast<decltype(v)&&>(v);
};
template<typename Key, typename Value, auto KeyFunc = noop>
class MyClass {
// ...
};
MyClass<
int,
Type,
[](Type t) {
return t.theVal();
}
> instance;
C++20 Live example
C++17 Live example

Is there a way to convert a list of lvalues and rvalues to a tuple with reference types and full types respectively?

So given the following 2 functions:
int rvalue();
int& lvalue();
The following would be valid:
std::tuple<int&, int> x = appropriate_fn(lvalue(), rvalue());
I was thinking something like it like this:
template <typename T, typename...Ts>
auto make_comparible(T const& arg, Ts&&...args)
{
return std::make_tuple(T(arg), make_comparible(args...));
}
template <typename T, typename...Ts>
auto make_comparible(T& arg, Ts&&...args)
{
return std::make_tuple<T&>(arg, make_comparible(args...));
}
template <typename T>
auto make_comparible(T const& arg)
{
return std::tuple<T>(T(arg));
}
template <typename T>
auto make_comparible(T& arg)
{
return std::tuple<T&>(arg);
}
But there's three issues this that I can see.
This is not a simple std::tuple, but a nested one. Which, come to think about it, may not be an issue as I just want to do comparisons (less than, equal to) on it and should still work.
This doesn't distinguish between a temporary and a const reference. This is a bit annoying but I don't see any way around it.
Most importantly, it doesn't work. Given the following:
std::tuple<int, std::tuple<int&>> x = make_comparible(rvalue(), lvalue());
std::tuple<int&, std::tuple<int>> y = make_comparible(lvalue(), rvalue());
The first one is works, but the second one give an error because make_comparible() is returning std::tuple<int, std::tuple<int&>> instead of std::tuple<int&, std::tuple<int>>. Demo
So, is what I'm asking for possible, or is it a pipe dream?
The use case is that I want to define a function in a class that will return a tuple (or otherwise comparable type), which will not result in a dangling pointer/reference, and which will be easy to use.
EDIT
Ok, so after watching C++ and Beyond 2012: Scott Meyers - Universal References in C++11, it looks like overloading on a universal reference is almost always an error. But as I am trying to differentiate a lvalue from an rvalue, irrespective of constness, this would be the right way to go.
I can use overloading to get the universal reference to bind to only rvalues, if I declare overloads which are for lvalues. I had also forgotten to use std::forward() which was a brain fart on my part. I should have known better.
#include <iostream>
#include <tuple>
// forward declarations
template <typename T, typename...Ts>
decltype(auto) make_comparible(T const& arg, Ts&&...args);
template <typename T, typename...Ts>
decltype(auto) make_comparible(T& arg, Ts&&...args);
template <typename T>
decltype(auto) make_comparible(T&& arg);
template <typename T>
decltype(auto) make_comparible(T const& arg);
template <typename T>
decltype(auto) make_comparible(T& arg);
// rvalue
template <typename T, typename...Ts>
decltype(auto) make_comparible(T&& arg, Ts&&...args)
{
std::cout << "rvalue ";
// want to copy, so do not use std::move()
return std::make_tuple(arg, make_comparible(std::forward<Ts>(args)...));
}
// lvalue const
template <typename T, typename...Ts>
decltype(auto) make_comparible(T const& arg, Ts&&...args)
{
std::cout << "lvalue const ref ";
// This is a reference, so store as a reference
return std::make_tuple<T const&>(arg, make_comparible(std::forward<Ts>(args)...));
}
// lvalue
template <typename T, typename...Ts>
decltype(auto) make_comparible(T& arg, Ts&&...args)
{
std::cout << "lvalue ref ";
// This is a reference, so store as a reference
return std::make_tuple<T&>(arg, make_comparible(std::forward<Ts>(args)...));
}
// rvalue
template <typename T>
decltype(auto) make_comparible(T&& arg)
{
std::cout << "rvalue ";
// want to copy, so do not use std::move()
return std::tuple<T>(arg);
}
// lvalue const
template <typename T>
decltype(auto) make_comparible(T const& arg)
{
std::cout << "lvalue const ref ";
// This is a reference, so store as a reference
return std::tuple<T const&>(arg);
}
// lvalue
template <typename T>
decltype(auto) make_comparible(T& arg)
{
std::cout << "lvalue ref ";
// This is a reference, so store as a reference
return std::tuple<T&>(arg);
}
int var = 5;
int rvalue() { return 4; }
int& lvalue() { return var; }
int const& const_lvalue() { return var; }
int main()
{
// expect output "rvalue lvalue ref", OK
std::tuple<int, std::tuple<int&>> x = make_comparible(rvalue(), lvalue());
std::cout << std::endl;
// expect output "rvalue lvalue const ref", OK
std::tuple<int, std::tuple<int const&>> y = make_comparible(rvalue(), const_lvalue());
std::cout << std::endl;
// expect output "lvalue ref lvalue const ref rvalue", OK
make_comparible(lvalue(), const_lvalue(), rvalue());
// But this doesn't work. Type returned was std::tuple<int, std::tuple<int, std::tuple<int> > >. WHY?
std::tuple<int&, std::tuple<int const&, std::tuple<int>>> z = make_comparible(lvalue(), const_lvalue(), rvalue());
std::cout << std::endl;
return 0;
}
So the code path is correct. But the type returned is wrong. I'm getting a std::tuple<int, std::tuple<int, std::tuple<int>>> instead of a std::tuple<int&, std::tuple<int const&, std::tuple<int>>>. WHY?
If I understand correctly what do you want, you can use std::reference (to wrap a l-value reference so that std::make_tuple() produce std::tuple with a reference in the corresponding position), and std::forward, to get the correct type of reference from a variadic list of arguments.
So you can write a couple of convert functions
int rVal ()
{ return 0; }
int & lVal ()
{ static int val { 1 }; return val; }
and make_comparable() become
template <typename ... Ts>
auto make_comparible (Ts && ... args)
{ return std::make_tuple(convert(std::forward<Ts>(args))...); }
if you can use C++14/C++17 (auto return type), or
template <typename ... Ts>
auto make_comparible (Ts && ... args)
-> decltype(std::make_tuple(convert(std::forward<Ts>(args))...))
{ return std::make_tuple(convert(std::forward<Ts>(args))...); }
or also (simpler)
template <typename ... Ts>
auto make_comparible (Ts && ... args)
-> decltype(std::make_tuple(convert(std::forward<Ts>(args))...))
{ return { convert(std::forward<Ts>(args))... }; }
if you must use C++11 (auto plus decltype(); ugly but works).
The following is a full working (C++14) example.
#include <tuple>
#include <functional>
int rVal ()
{ return 0; }
int & lVal ()
{ static int val { 1 }; return val; }
template <typename T>
std::reference_wrapper<T> convert (T & t)
{ return t; }
template <typename T>
T convert (T && t)
{ return std::move(t); }
template <typename ... Ts>
auto make_comparible (Ts && ... args)
{ return std::make_tuple(convert(std::forward<Ts>(args))...); }
int main ()
{
auto t = make_comparible(rVal(), lVal());
static_assert(std::is_same<std::tuple<int, int&>, decltype(t)>{}, "!");
}
Based on #max66's answer, I created a class which stores constructor arguments as a tuple member variable (lvalues as references and rvalues are moved):
#include <type_traits>
#include <tuple>
#include <functional>
#include <utility>
int rVal() { return 0; }
int& lVal() { static int val{1}; return val; }
template<typename T>
struct add_reference_wrapper{ using type = T; };
template<typename T>
struct add_reference_wrapper<T&> : public add_reference_wrapper<std::reference_wrapper<T>>{};
template<typename T>
using add_reference_wrapper_t = typename add_reference_wrapper<T>::type;
template<typename T>
struct remove_reference_wrapper{ using type = T; };
template<typename T>
struct remove_reference_wrapper<std::reference_wrapper<T>> : public remove_reference_wrapper<T&>{};
template<typename T>
using remove_reference_wrapper_t = typename remove_reference_wrapper<T>::type;
template<typename... Args>
struct S{
S(Args&&... args_) : args{std::forward<Args>(args_)...} {}
std::tuple<remove_reference_wrapper_t<Args>...> args;
};
template<typename... Args>
S(Args&&...) -> S<add_reference_wrapper_t<Args>...>;
int main() {
S s{rVal(), lVal()};
static_assert(std::is_same_v<decltype(s.args), std::tuple<int, int&>>);
}

Constexpr lambda implementation - how does this work?

while doing one task I came up across this tricky implementation that would allow you to make constexpr lambdas (which is not allowed out of the box):
Crazy constexpr lambdas implementation article
It basicly boils down to this implementation:
template<class F>
struct wrapper
{
//static_assert(std::is_empty<F>(), "Lambdas must be empty");
template<class... Ts>
decltype(auto) operator()(Ts&&... xs) const
{
return reinterpret_cast<const F&>(*this)(std::forward<Ts>(xs)...);
}
};
struct wrapper_factor
{
template<class F>
constexpr wrapper<F> operator += (F*)
{
return{};
}
};
struct addr_add
{
template<class T>
friend typename std::remove_reference<T>::type* operator+(addr_add, T &&t)
{
return &t;
}
};
#define STATIC_LAMBDA wrapper_factor() += true ? nullptr : addr_add() + []
usage
const constexpr auto add_one = STATIC_LAMBDA (/*i believe you can pass arguments here but i havent tried*/) -> bool
{
//do stuff
return stuff;
};
I have got 2 questions:
I needed to comment out static_assert due to "std::is_empty<_Ty>': no appropriate default constructor available" even without making any instance of this lambda. Anyone knows why?
How does this even work? I followed the flow of all the classes and I understand that
a) this part
true ? nullptr : addr_add() + [] your_lambda
returns a nullptr of a type we want lambda to be (further refered to as "correct_type" )
b) wrapper_factory takes this nullptr of correct_type, constructs wrapper. This wrapper is default initialized.
c) wrapper in operator() forwards the arguments that he is called with (passed in the /* i believe.../* place) to an object that is "wrapper this pointer casted to correct_type".
Now where is the information what routine to call actually passed? I can only see default created empty wrapper that is reinterpreted to some lambda type and called via ().
Best regards
Marcin K.
So consider this code
#include <type_traits>
template<class F>
struct wrapper
{
static_assert(std::is_empty<F>::value, "Lambdas must be empty");
template<class... Ts>
decltype(auto) operator()(Ts&&... xs) const
{
return reinterpret_cast<const F&>(*this)(std::forward<Ts>(xs)...);
}
};
struct wrapper_factor
{
template<class F>
constexpr wrapper<F> operator += (F*)
{
return{};
}
};
struct addr_add
{
template<class T>
friend typename std::remove_reference<T>::type *operator+(addr_add, T &&t)
{
return &t;
}
};
#define STATIC_LAMBDA wrapper_factor() += true ? nullptr : addr_add() + []
const constexpr auto add_one = STATIC_LAMBDA(auto x)
{
return x + 1;
};
int main()
{
int a = 1;
int b = add_one(a);
return 0;
}
So in MSVS2015, add_one is reported as a constexpr wrapper<type> add_one = {}
But in main, add_one(a) is reported as int wrapper<lambda []auto (auto x)->auto>::operator() <int &>(int &xs) const
And you can see that the friend function *operator+ in addr_add returns the pointer to the lambda function. And the lambda function definition is implicit. The variadic template in the wrapper struct operator() will mimic the lambda arguments.
Is this what you wanted to know?

Restrict functor parameter type and constness

I am trying to implement a resource protection class which would combine data along with a shared mutex (actually, QReadWriteLock, but it's similar). The class must provide the method to apply a user-defined function to the data when the lock is acquired. I would like this apply method to work differently depending on the function parameter (reference, const reference, or value). For example, when the user passes a function like int (const DataType &) it shouldn't block exclusively as we are just reading the data and, conversely, when the function has the signature like void (DataType &) that implies data modification, hence the exclusive lock is needed.
My first attempt was to use std::function:
template <typename T>
class Resource1
{
public:
template <typename Result>
Result apply(std::function<Result(T &)> &&f)
{
QWriteLocker locker(&this->lock); // acquire exclusive lock
return std::forward<std::function<Result(T &)>>(f)(this->data);
}
template <typename Result>
Result apply(std::function<Result(const T &)> &&f) const
{
QReadLocker locker(&this->lock); // acquire shared lock
return std::forward<std::function<Result (const T &)>>(f)(this->data);
}
private:
T data;
mutable QReadWriteLock lock;
};
But std::function doesn't seem to restrict parameter constness, so std::function<void (int &)> can easily accept void (const int &), which is not what I want. Also in this case it can't deduce lambda's result type, so I have to specify it manually:
Resource1<QList<int>> resource1;
resource1.apply<void>([](QList<int> &lst) { lst.append(11); }); // calls non-const version (ok)
resource1.apply<int>([](const QList<int> &lst) -> int { return lst.size(); }); // also calls non-const version (wrong)
My second attempt was to use std::result_of and return type SFINAE:
template <typename T>
class Resource2
{
public:
template <typename F>
typename std::result_of<F (T &)>::type apply(F &&f)
{
QWriteLocker locker(&this->lock); // lock exclusively
return std::forward<F>(f)(this->data);
}
template <typename F>
typename std::result_of<F (const T &)>::type apply(F &&f) const
{
QReadLocker locker(&this->lock); // lock non-exclusively
return std::forward<F>(f)(this->data);
}
private:
T data;
mutable QReadWriteLock lock;
};
Resource2<QList<int>> resource2;
resource2.apply([](QList<int> &lst) {lst.append(12); }); // calls non-const version (ok)
resource2.apply([](const QList<int> &lst) { return lst.size(); }); // also calls non-const version (wrong)
Mainly the same thing happens: as long as the object is non-const the mutable version of apply gets called and result_of doesn't restrict anything.
Is there any way to achieve this?
You may do the following
template <std::size_t N>
struct overload_priority : overload_priority<N - 1> {};
template <> struct overload_priority<0> {};
using low_priority = overload_priority<0>;
using high_priority = overload_priority<1>;
template <typename T>
class Resource
{
public:
template <typename F>
auto apply(F&& f) const
// -> decltype(apply_impl(std::forward<F>(f), high_priority{}))
{
return apply_impl(std::forward<F>(f), high_priority{});
}
template <typename F>
auto apply(F&& f)
// -> decltype(apply_impl(std::forward<F>(f), high_priority{}))
{
return apply_impl(std::forward<F>(f), high_priority{});
}
private:
template <typename F>
auto apply_impl(F&& f, low_priority) -> decltype(f(std::declval<T&>()))
{
std::cout << "ReadLock\n";
return std::forward<F>(f)(this->data);
}
template <typename F>
auto apply_impl(F&& f, high_priority) -> decltype(f(std::declval<const T&>())) const
{
std::cout << "WriteLock\n";
return std::forward<F>(f)(this->data);
}
private:
T data;
};
Demo
Jarod has given a workaround, but I'll explain why you cannot achieve that this regular way.
The problem is that:
Overload resolution prefers non-const member functions over const member functions when called from a non-const object
whatever object this signature void foo(A&) can accept, void foo(const A&) can also the same object. The latter even has a broader binding set than the former.
Hence, to solve it, you will have to at least defeat point 1 before getting to 2. As Jarod has done.
From your signatures (see my comment annotations):
template <typename F>
typename std::result_of<F (T &)>::type apply(F &&f) //non-const member function
{
return std::forward<F>(f)(this->data);
}
template <typename F>
typename std::result_of<F (const T &)>::type apply(F &&f) const //const member function
{
return std::forward<F>(f)(this->data);
}
When you call it like:
resource2.apply([](QList<int> &lst) {lst.append(12); }); //1
resource2.apply([](const QList<int> &lst) { return lst.size(); }); //2
First of all, remember that resource2 isn't a const reference. Hence, the non-const membr function of apply will always be prefered by Overload resolution.
Now, taking the case of the first call //1, Whatever that lambda is callable with, then then the second one is also callable with that object
A simplified mock-up of what you are trying to do is:
struct A{
template<typename Func>
void foo(Func&& f); //enable if we can call f(B&);
template<typename Func>
void foo(Func&& f) const; //enable if we can call f(const B&);
};
void bar1(B&);
void bar2(const B&);
int main(){
A a;
a.foo(bar1);
a.foo(bar2);
//bar1 and bar2 can be both called with lvalues
B b;
bar1(b);
bar2(b);
}
As I understand it, you want to discriminate a parameter that's a std::function that takes a const reference versus a non-constant reference.
The following SFINAE-based approach seems to work, using a helper specialization class:
#include <functional>
#include <iostream>
template<typename ...Args>
using void_t=void;
template<typename Result,
typename T,
typename lambda,
typename void_t=void> class apply_helper;
template <typename T>
class Resource1
{
public:
template <typename Result, typename lambda>
Result apply(lambda &&l)
{
return apply_helper<Result, T, lambda>::helper(std::forward<lambda>(l));
}
};
template<typename Result, typename T, typename lambda, typename void_t>
class apply_helper {
public:
static Result helper(lambda &&l)
{
std::cout << "T &" << std::endl;
T t;
return l(t);
}
};
template<typename Result, typename T, typename lambda>
class apply_helper<Result, T, lambda,
void_t<decltype( std::declval<lambda>()( std::declval<T>()))>> {
public:
static Result helper(lambda &&l)
{
std::cout << "const T &" << std::endl;
return l( T());
}
};
Resource1<int> test;
int main()
{
auto lambda1=std::function<char (const int &)>([](const int &i)
{
return (char)i;
});
auto lambda2=std::function<char (int &)>([](int &i)
{
return (char)i;
});
auto lambda3=[](const int &i) { return (char)i; };
auto lambda4=[](int &i) { return (char)i; };
test.apply<char>(lambda1);
test.apply<char>(lambda2);
test.apply<char>(lambda3);
test.apply<char>(lambda4);
}
Output:
const T &
T &
const T &
T &
Demo
The helper() static class in the specialized class can now be modified to take a this parameter, instead, and then use it to trampoline back into the original template's class's method.
As long as the capture lists of your lambdas are empty, you can rely on the fact that such a lambda decays to a function pointer.
It's suffice to discriminate between the two types.
It follows a minimal, working example:
#include<iostream>
template <typename T>
class Resource {
public:
template <typename Result>
Result apply(Result(*f)(T &)) {
std::cout << "non-const" << std::endl;
return f(this->data);
}
template <typename Result>
Result apply(Result(*f)(const T &)) const {
std::cout << "const" << std::endl;
return f(this->data);
}
private:
T data;
};
int main() {
Resource<int> resource;
resource.apply<void>([](int &lst) { });
resource.apply<int>([](const int &lst) -> int { return 42; });
}