I'm trying to take a 'task' in the style of std::async and store it in a container. I'm having to jump through hoops to achieve it, but I think there must be a better way.
std::vector<std::function<void()>> mTasks;
template<class F, class... Args>
std::future<typename std::result_of<typename std::decay<F>::type(typename std::decay<Args>::type...)>::type>
push(F&& f, Args&&... args)
{
auto func = std::make_shared<std::packaged_task<typename std::result_of<typename std::decay<F>::type(typename std::decay<Args>::type...)>::type()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));
auto future = func->get_future();
// for some reason I get a compilation error in clang if I get rid of the `=, ` in this capture:
mTasks.push_back([=, func = std::move(func)]{ (*func)(); });
return future;
}
So I'm using bind -> packaged_task -> shared_ptr -> lambda -> function. How can I do this better/more optimally? It would certainly be easier if there was a std::function which could take a non-copyable but moveable task. Can I std::forward args into the capture of a lambda, or do I have to use bind?
There is no kill like overkill.
Step 1: write a SFINAE friendly std::result_of and a function to help calling via tuple:
namespace details {
template<size_t...Is, class F, class... Args>
auto invoke_tuple( std::index_sequence<Is...>, F&& f, std::tuple<Args>&& args)
{
return std::forward<F>(f)( std::get<Is>(std::move(args)) );
}
// SFINAE friendly result_of:
template<class Invocation, class=void>
struct invoke_result {};
template<class T, class...Args>
struct invoke_result<T(Args...), decltype( void(std::declval<T>()(std::declval<Args>()...)) ) > {
using type = decltype( std::declval<T>()(std::declval<Args>()...) );
};
template<class Invocation, class=void>
struct can_invoke:std::false_type{};
template<class Invocation>
struct can_invoke<Invocation, decltype(void(std::declval<
typename invoke_result<Inocation>::type
>()))>:std::true_type{};
}
template<class F, class... Args>
auto invoke_tuple( F&& f, std::tuple<Args>&& args)
{
return details::invoke_tuple( std::index_sequence_for<Args...>{}, std::forward<F>(f), std::move(args) );
}
// SFINAE friendly result_of:
template<class Invocation>
struct invoke_result:details::invoke_result<Invocation>{};
template<class Invocation>
using invoke_result_t = typename invoke_result<Invocation>::type;
template<class Invocation>
struct can_invoke:details::can_invoke<Invocation>{};
We now have invoke_result_t<A(B,C)> which is a SFINAE friendly result_of_t<A(B,C)> and can_invoke<A(B,C)> which just does the check.
Next, write a move_only_function, a move-only version of std::function:
namespace details {
template<class Sig>
struct mof_internal;
template<class R, class...Args>
struct mof_internal {
virtual ~mof_internal() {};
// 4 overloads, because I'm insane:
virtual R invoke( Args&&... args ) const& = 0;
virtual R invoke( Args&&... args ) & = 0;
virtual R invoke( Args&&... args ) const&& = 0;
virtual R invoke( Args&&... args ) && = 0;
};
template<class F, class Sig>
struct mof_pimpl;
template<class R, class...Args, class F>
struct mof_pimpl<F, R(Args...)>:mof_internal<R(Args...)> {
F f;
virtual R invoke( Args&&... args ) const& override { return f( std::forward<Args>(args)... ); }
virtual R invoke( Args&&... args ) & override { return f( std::forward<Args>(args)... ); }
virtual R invoke( Args&&... args ) const&& override { return std::move(f)( std::forward<Args>(args)... ); }
virtual R invoke( Args&&... args ) && override { return std::move(f)( std::forward<Args>(args)... ); }
};
}
template<class R, class...Args>
struct move_only_function<R(Args)> {
move_only_function(move_only_function const&)=delete;
move_only_function(move_only_function &&)=default;
move_only_function(std::nullptr_t):move_only_function() {}
move_only_function() = default;
explicit operator bool() const { return pImpl; }
bool operator!() const { return !*this; }
R operator()(Args...args) & { return pImpl().invoke(std::forward<Args>(args)...); }
R operator()(Args...args)const& { return pImpl().invoke(std::forward<Args>(args)...); }
R operator()(Args...args) &&{ return std::move(*this).pImpl().invoke(std::forward<Args>(args)...); }
R operator()(Args...args)const&&{ return std::move(*this).pImpl().invoke(std::forward<Args>(args)...); }
template<class F,class=std::enable_if_t<can_invoke<decay_t<F>(Args...)>>
move_only_function(F&& f):
m_pImpl( std::make_unique<details::mof_pimpl<std::decay_t<F>, R(Args...)>>( std::forward<F>(f) ) )
{}
private:
using internal = details::mof_internal<R(Args...)>;
std::unique_ptr<internal> m_pImpl;
// rvalue helpers:
internal & pImpl() & { return *m_pImpl.get(); }
internal const& pImpl() const& { return *m_pImpl.get(); }
internal && pImpl() && { return std::move(*m_pImpl.get()); }
internal const&& pImpl() const&& { return std::move(*m_pImpl.get()); } // mostly useless
};
not tested, just spewed the code. The can_invoke gives the constructor basic SFINAE -- you can add "return type converts properly" and "void return type means we ignore the return" if you like.
Now we rework your code. First, your task are move-only functions, not functions:
std::vector<move_only_function<X>> mTasks;
Next, we store the R type calculation once, and use it again:
template<class F, class... Args, class R=std::result_of_t<std::decay<F>_&&(std::decay_t<Args>&&...)>>
std::future<R>
push(F&& f, Args&&... args)
{
auto tuple_args=std::make_tuple(std::forward<Args>(args)...)];
// lambda will only be called once:
std::packaged_task<R()> task([f=std::forward<F>(f),args=std::move(tuple_args)]
return invoke_tuple( std::move(f), std::move(args) );
});
auto future = func.get_future();
// for some reason I get a compilation error in clang if I get rid of the `=, ` in this capture:
mTasks.emplace_back( std::move(task) );
return future;
}
we stuff the arguments into a tuple, pass that tuple into a lambda, and invoke the tuple in a "only do this once" kind of way within the lambda. As we will only invoke the function once, we optimize the lambda for that case.
A packaged_task<R()> is compatible with a move_only_function<R()> unlike a std::function<R()>, so we can just move it into our vector. The std::future we get from it should work fine even though we got it before the move.
This should reduce your overhead by a bit. Of course, there is lots of boilerplate.
I have not compiled any of the above code, I just spewed it out, so the odds it all compiles are low. But the errors should mostly be tpyos.
Randomly, I decided to give move_only_function 4 different () overloads (rvalue/lvalue and const/not). I could have added volatile, but that seems reckless. Which increase boilerplate, admittedly.
Also my move_only_function lacks the "get at the underlying stored stuff" operation that std::function has. Feel free to type erase that if you like. And it treats (R(*)(Args...))0 as if it was a real function pointer (I return true when cast to bool, not like null: type erasure of convert-to-bool might be worthwhile for a more industrial quality implementation.
I rewrote std::function because std lacks a std::move_only_function, and the concept in general is a useful one (as evidenced by packaged_task). Your solution makes your callable movable by wrapping it with a std::shared_ptr.
If you don't like the above boilerplate, consider writing make_copyable(F&&), which takes an function object F and wraps it up using your shared_ptr technique to make it copyable. You can even add SFINAE to avoid doing it if it is already copyable (and call it ensure_copyable).
Then your original code would be cleaner, as you'd just make the packaged_task copyable, then store that.
template<class F>
auto make_function_copyable( F&& f ) {
auto sp = std::make_shared<std::decay_t<F>>(std::forward<F>(f));
return [sp](auto&&...args){return (*sp)(std::forward<decltype(args)>(args)...); }
}
template<class F, class... Args, class R=std::result_of_t<std::decay<F>_&&(std::decay_t<Args>&&...)>>
std::future<R>
push(F&& f, Args&&... args)
{
auto tuple_args=std::make_tuple(std::forward<Args>(args)...)];
// lambda will only be called once:
std::packaged_task<R()> task([f=std::forward<F>(f),args=std::move(tuple_args)]
return invoke_tuple( std::move(f), std::move(args) );
});
auto future = func.get_future();
// for some reason I get a compilation error in clang if I get rid of the `=, ` in this capture:
mTasks.emplace_back( make_function_copyable( std::move(task) ) );
return future;
}
this still requires the invoke_tuple boilerplate above, mainly because I dislike bind.
Related
Lately I wrote a template function to solve some code repetitions. It looks like this:
template<class T, class R, class... Args>
R call_or_throw(const std::weak_ptr<T>& ptr, const std::string& error, R (T::*fun)(Args...), Args... args) {
if (auto sp = ptr.lock())
{
return std::invoke(fun, *sp, args...);
}
else
{
throw std::runtime_error(error.c_str());
}
}
int main() {
auto a = std::make_shared<A>();
call_or_throw(std::weak_ptr<A>(a), "err", &A::foo, 1);
}
This code works perfectly well for class A which looks like this:
class A {
public:
void foo(int x) {
}
};
But fails to compile for one like this:
class A {
public:
void foo(const int& x) {
}
};
Why is it so (by why I mean why it fails to deduce the type) and how (if it is possible at all) can I make this code work with references?
Live example
Args types cannot be deduced both as const& (from fun parameter declaration) and non-reference from args declaration. A simple fix is to use two separate template type parameter packs:
template<class T, class R, class... Args, class... DeclaredArgs>
R call_or_throw(
const std::weak_ptr<T>& ptr,
const std::string& error,
R (T::*fun)(DeclaredArgs...),
Args... args);
As a downside, I can imagine slightly longer error messages in case of bad usage.
Note that the template parameter Args's type is deduced as const int& on the 3rd function argument &A::foo, and deduced as int on the 4th function parameter 1. They don't match and cause deduction fails.
You can exclude the 4th parameter from deduction, e.g.
template<class T, class R, class... Args>
R call_or_throw(const std::weak_ptr<T>& ptr,
const std::string& error,
R (T::*fun)(Args...),
std::type_identity_t<Args>... args) {
// ^^^^^^^^^^^^^^^^^^^^^^^^^^
LIVE
PS: std::type_identity is supported since C++20; but it's quite easy to implement one.
Your issue is that you have conflict deductions for Args between:
R (T::*fun)(Args...)
Args... args
I suggest to have more generic code (no duplications between R (T::*fun)(Args...) and
const version R (T::*fun)(Args...) const and other alternative) with:
template<class T, class F, class... Args>
decltype(auto) call_or_throw(const std::weak_ptr<T>& ptr,
const std::string& error,
F f,
Args&&... args)
{
if (auto sp = ptr.lock())
{
return std::invoke(f, *sp, std::forward<Args>(args)...);
}
else
{
throw std::runtime_error(error.c_str());
}
}
How the compiler interpret the symbol _1, and how the binding take place?
Consider the following example:
class A {
public:
boost::function<void (int x)> g;
};
class B {
public:
B() {}
static void foo(int i) { cout << "Hack: " << i <<endl; }
};
int main() {
A a;
a.g = boost::bind(B::foo,_1);
a.g(2);
return 0;
}
What magic happens internally in the line boost::bind(B::foo,_1);?
And how _1 is maped to the argument passed in the next line a.g(2);?
Output: Hack: 2
I will explain to the best of my ability. First and foremost, _1 is nothing but a global variable. There is nothing special about it in this regard, and it could be named anything else as well - placeholder1, or SergeyA. However, name like _1 is short, has well-understood meaning, and begins with _, which reduces likelihood of it clashing with other global names in the program.
The magic is in the type of this variable. It has a special type, which is reflected in generated bind* object. Later, when operator() is called, the type is recognized to take an argument from operator() arguments.
Here is some illustrating C++-like pseudocode, which is not correct, but is illustrative:
template<class F, class... ARG>
struct bound {
bound(F f, ARGS&&... args) : bound_args(args...), functor(f) { }
std::tuple<ARG...> bound_args;
template<class... T>
void operator()(T&&... args);
F f;
};
template<class F, class... T>
auto bind(F f, T&& args) {
return bound<std::remove_reference_t<T>...>(f, args...);
}
Now, let's introduce a placeholder type.
template<size_t N>
struct placeholder {
enum { position = N; };
template<class...T>
auto operator()(T&&... args) {
return std::get<position>(std::make_tuple(arg...));
}
};
placeholder<0> _1;
placeholder<1> _2;
So far so good. Now, let's see how the operator() actually works on bound object:
template<class... BOUND_ARGS>
template<class... CALL_ARGS>
void bound_object<BOUND_ARGS...>::operator() (CALL_ARGS&&... args) {
call_impl(args..., make_index_sequence<sizeof...(BOUND_ARGS)>{});
}
make_index_sequence here is needed to extract tuple values into function arguments, so do not pay too much attention to it. And here is call_impl;
template<class... BOUND_ARGS>
template<class... CALL_ARGS, size_t... ix>
void bound_object<BOUND_ARGS...>::call_impl(CALL_ARGS&&... args, std::index_sequence<ix...>) {
f(to_arg().(std::get<ix>(bound_args), args...)...);
}
And the last piece of puzzle is to_arg:
template<class B, class... ARGS>
auto to_arg(B&& b, ARGS... args) {
return b;
}
template<class... ARGS>
auto to_arg(placeholder<0> p, ARGS&&... args) {
return p(args);
}
template<class... ARGS>
auto to_arg(placeholder<1> p, ARGS&&... args) {
return p(args);
}
The whole of to_arg here is to give you either the bound argument or one of the supplied arguments, based on the bound argument type. In my example above, I used 3 overloads since you can partially specialize a function, but of course, it would make more sense to put it in a class and partially specialize the class.
I'm trying to write the following factory class, but I can't find the proper syntax:
template<class T, typename... TArgs>
class Factory {
public:
Factory(TArgs... args) {
creator_ = std::bind(&std::make_shared<T, TArgs...>, args...);
// ^^^ some error around here
}
std::shared_ptr<T> Create() const {
return creator_();
}
private:
std::function<std::shared_ptr<T>()> creator_;
};
This is how I use the factory:
class Foo {
public:
Foo(bool value) {}
};
class Bar {
public:
Bar(const std::string& value) {}
};
Factory<Foo, bool> f1(true);
Factory<Bar, std::string> f2("string");
These are the errors I get when declaring f1 and f2:
error: no match for 'operator=' (operand types are 'std::function<std::shared_ptr<Foo>()>' and 'std::_Bind_helper<false, std::shared_ptr<Foo> (*)(bool&&), bool&>::type {aka std::_Bind<std::shared_ptr<Foo> (*(bool))(bool&&)>}')
creator_ = std::bind(&std::make_shared<T, TArgs...>, args...);
^
error: no match for 'operator=' (operand types are 'std::function<std::shared_ptr<Bar>()>' and 'std::_Bind_helper<false, std::shared_ptr<Bar> (*)(std::basic_string<char>&&), std::basic_string<char, std::char_traits<char>, std::allocator<char> >&>::type {aka std::_Bind<std::shared_ptr<Bar> (*(std::basic_string<char>))(std::basic_string<char>&&)>}')
creator_ = std::bind(&std::make_shared<T, TArgs...>, args...);
^
What is the correct syntax I must use with std::bind?
std::make_shared is declared like this:
template< class T, class... Args >
shared_ptr<T> make_shared( Args&&... args );
As such, std::make_shared<T, TArgs...> will result in a function taking rvalue references, which won't bind to args.... A simple fix for this is to force it to take lvalue references by collapsing the reference:
creator_ = std::bind(&std::make_shared<T,TArgs&...>, args...);
// ^
An alternative is to use a lambda instead, which is more readable:
creator_ = [=](){return std::make_shared<T>(args...);};
So, a maximally efficient C++14 solution that doesn't use bind is actually awkward.
template<class T>
struct Factory {
template<class...Args>
Factory(Args&&... args):
creator_(
make_creator(
std::index_sequence_for<Args...>{},
std::make_tuple( std::forward<Args>(args)...
)
)
{}
std::shared_ptr<T> operator()() const {
return creator_();
}
private:
using signature = std::shared_ptr<T>();
using creator = std::function<signature>;
creator creator_;
// helper, to make a lambda with a tuple to unpack:
template<class Tup, size_t...Is>
static creator make_creator(std::index_sequence<Is...>, Tup&& tup) {
return [tup = std::forward<Tup>(tup)]{
return std::make_shared<T>( std::get<Is>(tup)... );
};
}
};
this version has a few improvements.
First, no need to specify arguments you create the T from:
Factory<Foo> f1(true);
Factory<Bar> f2("string");
Second, instead of f1.Create(), we have f1(). Invoking a factory clearly creates the thing the factory creates -- calling a named method is just noise.
We could go a step further:
template<class T>
using Factory = std::function<std::shared_ptr<T>()>;
namespace details {
template<class T, class Tup, size_t...Is>
Factory<T> make_factory(std::index_sequence<Is...>, Tup&& tup) {
return [tup = std::forward<Tup>(tup)]{
return std::make_shared<T>( std::get<Is>(tup)... );
};
}
}
template<class T, class...Args>
Factory<T> make_factory(Args&&...args) {
return details::make_factory<T>(
std::index_sequence_for<Args...>{},
std::make_tuple( std::forward<Args>(args)... )
);
}
where we do away with the Factory type entirely -- Factory<T> just becomes an alias for a std::function that takes nothing and returns a shared_ptr<T>.
live example.
Now I find details::make_factory to be boring.
namespace details {
template<class F, class Tup, size_t...Is>
auto invoke( F&& f, Tup&& tup, std::index_sequence<Is...> )
-> std::result_of_t<F( std::tuple_element_t<Is, std::decay_t<Tup>>... )>
{
return std::forward<F>(f)( std::get<Is>(std::forward<Tup>(tup))... );
}
}
template<class F, class Tup, size_t...Is>
auto invoke( F&& f, Tup&& tup )
{
using count = std::tuple_size< std::decay_t<Tup> >;
using indexes = std::make_index_sequence< count{} >;
return details::invoke(
std::forward<F>(f),
std::forward<Tup>(tup),
indexes{}
);
}
template<class T>
auto shared_maker() {
return [](auto&&...args){
return std::make_shared<T>( decltype(args)(args)... );
};
}
template<class T, class...Args>
Factory<T> make_factory(Args&&...args) {
return [tup=std::make_tuple(std::forward<Args>(args)...)]{
return invoke(
shared_maker<T>(),
tup
);
};
}
live example, where we take the 'invoke a function from a tuple' and write it as invoke separately.
template<class T>
const auto shared_maker = [](auto&&...args){
return std::make_shared<T>(decltype(args)(args)...);
};
would be slightly slicker, but gcc 5.2.0 doesn't like it.
how could one do this in c++ today without using two separate holders?
typedef std::function<void(int a, int b)> f1;
typedef std::function<void(int a)> f2;
std::vector<f1> m;
void add(f1 f)
{
m.push_back(f);
}
void add(f2 f)
{
// add one more (unused) parameter to f2 so we can add f2 to f1 vector holder?
}
can we somehow overload f1 function to include different set of parameters?
could this be solved by variadic templates nowdays or something similar?
Create a new lambda matching the new signature and add that instead:
void add(f2 f)
{
m.push_back( [g = std::move(f)](int a, int /* unused */){ g(a); } );
}
This wraps a std::function in a lambda that ignores any extra args:
template<class R, class...Args>
auto ignore_extra_args( std::function<R(Args...)> f ) {
return [f = std::move(f)](Args...args, auto&&...)->R{
return f(std::forward<Args>(args)...);
};
}
it gets trickier if we don't want that needless layer of type erasure.
You need to find the longest prefix of Args... which you can invoke a given object f with, then invoke f with it. This involves some tricky metaprogramming.
It is far easier if you ask the caller to pass in a signature:
template<class Sig>
struct ignore_extra_args_helper;
template<class R, class...Args>
struct ignore_extra_args_helper<R(Args...)> {
template<class F>
auto operator()( F&& f ) {
return [f = std::forward<F>(f)](Args...args, auto&&...)->R{
return f(std::forward<Args>(args)...);
};
}
};
template<class Sig, class F>
auto ignore_extra_args( F&& f ) {
return ignore_extra_args_helper<Sig>{}(std::forward<F>(f));
}
which saves on possible overhead.
template<class F, decltype(std::declval<F const&>()(1,1))* =nullptr>
void add(F&& f) {
m.push_back(std::forward<F>(f));
}
template<class F, class...Unused>
void add(F&& f, Unused&&...) {
add( ignore_extra_args<void(int)>(std::forward<F>(f)) );
}
live example
I would like to write function as this find:
multi_set<int, string, double, myType> m; //vector of tuples
m.insert(/*some data*/);
m.find<1,2>("something",2.123);
Or
m.find<0,3>(1,instanceOfMyType);
m.find<1>("somethingelse");
Where find can be parametrized corresponding to any subset of tuple parameters.
My code so far:
template <typename ... T>
class multi_set{
typedef tuple < T... > Tuple;
vector<tuple<T...>> data = vector<tuple<T...>>();
public:
void insert(T... t){
data.push_back(tuple<T...>(t...));
}
template<size_t ... Pos>
void find(???){
// then I would like to use those params to search through data and
// return first matching item
}
}
// test whether a particular tuple is a match
template<size_t... Pos>
static bool is_match(const Tuple& tuple, const typename std::tuple_element<Pos, Tuple>::type &... args) {
std::initializer_list<bool> results = { (std::get<Pos>(tuple) == args)... };
return std::all_of(results.begin(), results.end(), [](bool p) { return p; });
}
// Find the first one that is a match.
template<size_t... Pos>
typename vector<Tuple>::const_iterator find(const typename std::tuple_element<Pos, Tuple>::type &... args) const {
return std::find_if(data.begin(), data.end(), [&](const Tuple & tup) { return is_match<Pos...>(tup, args...); });
}
It's also possible to have find take a type parameter pack and perfectly forward, rather than taking fixed types with tuple_element. The benefit is that you can avoid an unnecessary conversion if == is transparent. The cost is that you can't take anything that can't be perfectly forwarded any more (e.g., braced initializer lists, 0 as a null pointer constant). A side benefit appears to be that MSVC 2013 doesn't choke on this version:
// test whether a particular tuple is a match
template<size_t... Pos, class... Args>
static bool is_match(const Tuple& tuple, Args&&... args) {
std::initializer_list<bool> results = { (std::get<Pos>(tuple) == std::forward<Args>(args))... };
return std::all_of(results.begin(), results.end(), [](bool p) { return p; });
}
// Find the first one that is a match.
template<size_t... Pos, class... Args>
typename vector<Tuple>::const_iterator find(Args&&... args) const {
return std::find_if(data.begin(), data.end(), [&](const Tuple & tup) { return is_match<Pos...>(tup, std::forward<Args>(args)...); });
}
You should look into boost::multi_index. It is very close to what you are looking for.
http://www.boost.org/doc/libs/1_54_0/libs/multi_index/doc/tutorial/index.html
This is a function that takes a seed value, and a set of lambdas. It feeds that seed value through each of the lambdas in turn:
template<class... Fs, class R>
R chain( R r, Fs&&... fs ) {
using in_order = int[];
(void)(in_order{0,
(
(r = std::forward<Fs>(fs)( r ))
, void(), 0
)...
});
return r;
}
Inside your class, we use the above:
template<size_t... Pos, class...Us>
typename std::vector<Tuple>::const_iterator
find(Us const&... us) const {
return std::find_if(
data.begin(), data.end(),
[&](const Tuple & tup) {
return chain(
true,
[&](bool old){
return old && (std::get<Pos>(tup) == us);
}...
);
}
);
}
this compiles in clang, but not g++ 4.9.2 -- g++ doesn't like parameter packs inside lambdas.
Note the fact we take Us const&... -- this allows for transparent ==, which is important in some cases. std::string == char const* is a classic example, where if you force find to take the same value as in the tuple, you'll force a needless allocation in calling find.
In C++1z, the chain call can be replaced with:
( ... && (std::get<Pos>(tup) == us) )
which is conceptually identical, but much easier to read. This is known as a "fold expression".
Now, a problem with the above is that it uses forwarding references, which causes imperfect forwarding problems of perfect forwarding.
The most annoying of which is the inability to use {} to construct arguments.
If we use matching types, we instead force non-transparent comparison, which can be expensive (examine std::string compared to "hello this is a c string" -- it causes possibly allocation if we force the c string into a std::string.)
A way around this is to type erase down to the concept of equality with a given type.
template<class...>struct voider{using type=void;};
template<class...Ts>using void_t=typename voider<Ts...>::type;
template<class T>struct tag{using type=T;};
template<class...>struct types{using type=types;};
template<class T>
using block_deduction = typename tag<T>::type;
template<class F, class Sig, class T=void>
struct erase_view_op;
template<class F, class R, class...Ts, class T>
struct erase_view_op<F, R(Ts...), T>
{
using fptr = R(*)(void const*, Ts&&...);
fptr f;
void const* ptr;
private:
template<class U>
erase_view_op(U&& u, int):
f([](void const* p, Ts&&...ts)->R{
U& u = reinterpret_cast<U&>( *static_cast<std::decay_t<U>*>(const_cast<void*>(p)) );
return F{}( u, std::forward<Ts>(ts)... );
}),
ptr( static_cast<void const*>(std::addressof(u)) )
{}
public:
template<class U, class=std::enable_if_t< !std::is_same<std::decay_t<U>,erase_view_op>{} && std::is_convertible< std::result_of_t<F(U,Ts...)>, R >{} >>
erase_view_op(U&& u):erase_view_op( std::forward<U>(u), 0 ){}
template<class U=T, class=std::enable_if_t< !std::is_same<U, void>{} >>
erase_view_op( block_deduction<U>&& u ):erase_view_op( std::move(u), 0 ){}
erase_view_op( erase_view_op const& ) = default;
erase_view_op( erase_view_op&& ) = default;
R operator()( Ts... ts ) const {
return f( ptr, std::forward<Ts>(ts)... );
}
};
struct equality {
template<class lhs, class rhs>
bool operator()(lhs const& l, rhs const& r)const {
return l==r;
}
};
template<class T>
using erase_equal_to = erase_view_op< equality, bool(T const&), T >;
using string_equal_to = erase_equal_to< std::string >;
int main() {
static_assert( std::is_same< bool, std::result_of_t< std::equal_to<>(decltype("hello"), std::string const&) > >{}, "hmm" );
string_equal_to s = "hello";
string_equal_to s2 = {{"hello"}};
(void)s2;
std::string x = "hello";
std::string y = "jello";
std::cout << s(x) << s(y) << '\n';
}
then we rewrite find:
template<size_t... Pos>
typename std::vector<Tuple>::const_iterator
find(erase_equal_to< std::remove_reference_t<std::tuple_element_t<Pos, Tuple>> >... us) const {
return std::find_if(
data.begin(), data.end(),
[&](const Tuple & tup) {
return chain(
true,
[&](bool old){
return old && us(std::get<Pos>(tup));
}...
);
}
);
}
which does both transparent equality and allows {} based construction (well, it does require {{}} based construction -- the outer to say we are constructing the eraser, the inner to construct the T).