Reputation: 91
I'm trying to retrieve values from a tuple of arrays using type information on the function used for processing them. However, type deduction fails for this case due (in part?) to the need to use an identity struct for the typename of the std::function
. Is there a way to restore deduction here?
#include <functional>
#include <iostream>
#include <tuple>
class comp_a
{
public:
static const size_t id = 0;
int val = 0;
};
class comp_b
{
public:
static const size_t id = 1;
int val = 0;
};
class comp_c
{
public:
static const size_t id = 2;
int val = 0;
};
template<size_t size, typename ... Cs>
struct storage
{
template<typename T> struct identity { using type = T; };
template<typename ... Ts>
void get(size_t index, typename identity<std::function<void(Ts& ...)>>::type f)
{
f(std::get<Ts::id>(components)[index] ...);
}
std::tuple<std::array<Cs, size> ...> components;
};
int32_t main()
{
storage<20, comp_a, comp_b, comp_c> storage;
storage.get(2, [](comp_a& a, comp_c& c) { // Doesn't work
// storage.get<comp_a, comp_c>(2, [](comp_a& a, comp_c& c) { // Works
std::cout << a.val << " " << c.val << std::endl;
});
}
I've run across this and this, which seem similar, but I believe my situation is different because I need the variadic types in order to access their traits when retrieving the desired values. As in those examples, the variadic parameters to the function are treated as void:
error: cannot convert 'main()::<lambda(comp_a&, comp_c&)>' to 'storage<20, comp_a, comp_b, comp_c>::identity<std::function<void()> >::type' {aka 'std::function<void()>'}
Would a deduction guide be viable in this situation? The type information seems buried a little deep in the type of the std::function
, and so I'm not sure how I would pull it out in a guide.
A live example is available here.
Upvotes: 4
Views: 410
Reputation: 303890
You can use the std::function
deduction guide trick to pull out the arguments, but you really don't want to actually create a std::function
. You want to stack in lambda-land. std::function
adds overhead and allocation due to type erasure - but there is nothing you're doing that actually requires the benefits that type erasure provides. It is all loss and no win. Don't actually make a std::function
.
That said, you still of course need the arguments. So you can do this:
template <typename T> struct type { };
template <typename F>
void get(size_t index, F f) {
using function_type = decltype(std::function(f));
get_impl(index, f, type<function_type>{});
}
Basically, we take some callable - and then we deduce a std::function
from it. That gives us some type. In the specific example in OP, that type is std::function<void(comp_a&, comp_b&)>
. We then simply forward that type through to a different function - as an empty object. No overhead. To repeat, we're not actually creating a std::function
- we're just passing its type around.
That other function can take advantage of knowing what std::function
knows:
template <typename T> using uncvref_t = std::remove_cv_t<std::remove_reference_t<T>>;
template <typename F, typename R, typename... Args>
void get_impl(size_t index, F f, type_t<std::function<R(Args...)>>) {
f(std::get<uncvref_t<Args>::id>(components)[index] ...);
}
You need the uncvref_t
there to handle the case where Args
may or may not be a reference or cv-qualified.
Now, this won't work for any callable. If you pass in a generic lambda, deducing std::function
will fail for it. But then... that couldn't work anyway, so it doesn't seem like a big loss?
Upvotes: 2
Reputation: 66230
You tagged C++17, so you can use the std::function
's deduction guides
So, as suggested by Alan Birtles, you can receive the lambda as a simple type, convert it to a std::function
(deduction guides) and deduce the type of the arguments.
Something as
template<size_t size, typename ... Cs>
struct storage
{
template<typename ... Ts>
void get(size_t index, std::function<void(Ts& ...)> f)
{ f(std::get<Ts::id>(components)[index] ...); }
template <typename F>
void get(size_t index, F f)
{ get(index, std::function{f}); }
std::tuple<std::array<Cs, size> ...> components;
};
Upvotes: 1
Reputation: 36578
I'm not sure what the identity
struct is for but removing it gives a clearer error message (template deduction failed).
The compiler is unable to derive the std::function
type from the lambda. To prove this the following does compile:
storage.get(2, std::function<void(comp_a& a, comp_c& c)>([](comp_a& a, comp_c& c) { // Doesn't work
std::cout << a.val << " " << c.val << std::endl;
}));
So to make it work we just need to give the compiler a helping hand deriving the types. Borrowing from http://www.cplusplus.com/forum/general/223816/ the following works:
namespace detail
{
template < typename T > struct deduce_type;
template < typename RETURN_TYPE, typename CLASS_TYPE, typename... ARGS >
struct deduce_type< RETURN_TYPE(CLASS_TYPE::*)(ARGS...) const >
{
using type = std::function< RETURN_TYPE(ARGS...) >;
};
}
template<size_t size, typename ... Cs>
struct storage
{
template<typename ... Ts>
void get(size_t index, typename std::function<void(Ts& ...)> f)
{
f(std::get<Ts::id>(components)[index] ...);
}
template<typename Lambda>
void get(size_t index, Lambda l)
{
get( index, typename detail::deduce_type< decltype( &Lambda::operator() ) >::type( l ) );
}
std::tuple<std::array<Cs, size> ...> components;
};
Upvotes: 1