Reputation: 239
For the purpose of a library I am currently writing, my goal is, given lambda function specified by the user, to call it internally with arguments of my choice.
So far it works properly and looks like this:
#include <tuple>
template<typename T> struct GetFinfos;
template<typename R, typename F, typename ...Args>
struct GetFinfos<R(F::*)(Args...) const> {
static std::tuple<Args...> createChosenTuple() {
return std::make_tuple(Args(1)...);
}
};
template<typename F>
void internal_lambda_call(const F & f) {
//let's suppose for convenience that we have a working call_from_tuple in C++14
std::apply(f, GetFinfos<decltype(&F::operator())>::createChosenTuple() );
}
int main()
{
auto f = [](int i, int j, int k) { };
internal_lambda_call(f); // calls f(1,1,1);
}
Now, C++14
introduced lambda functions with default arguments and I wonder if the library could use this extra bit of information from the user.
auto g = [](int i, int j = 5, int k = 5) { };
As I understand, lambdas with default parameters still have only one ClosureType::operator ret(*)(params)()
operator. Therefore the GetFinfos
is blind to the default parameters and internal_lambda_call(g)
calls g(1,1,1)
.
However, since we have access to the full argument types list, we could theoretically try to call the lambda with a decreasing number of arguments and pick the last valid one.
g(1,1,1) //valid call
g(1,1) //valid call
g(1); //valid call <---- last valid one
g(); //invalid call
So my question is: if there is any existing machinery so internal_lambda_call(g)
calls automatically either g(1)
or g(1,5,5)
? I see here that generic lambdas can be used to wrap the lambda and make it work with an explicit number of argument. But I don't see a way to get this minimal number of argument automatically.
Upvotes: 3
Views: 138
Reputation: 66200
I don't see a way to get this minimal number of argument automatically.
Maybe... with a little of recursion and SFINAE ...
#include <tuple>
#include <utility>
template <typename F, typename T, std::size_t ... Is>
constexpr auto gmna_helper (std::index_sequence<Is...> is, int)
-> decltype( (void)std::declval<F>()
(std::declval<std::tuple_element_t<Is, T>>()...),
std::size_t{} )
{ return sizeof...(Is); }
template <typename F, typename T, std::size_t ... Is>
constexpr auto gmna_helper (std::index_sequence<Is...>, long)
{ return gmna_helper<F, T>(std::make_index_sequence<sizeof...(Is)+1u>{}, 0); }
template <typename F, typename ... Ts>
constexpr std::size_t getMinNumArgs ()
{ return gmna_helper<F, std::tuple<Ts...>>(std::index_sequence<>{}, 0); }
int main ()
{
auto g = [](int, int = 5, int = 5) { };
constexpr auto n = getMinNumArgs<decltype(g), int, int, int>();
static_assert( n == 1, "!" );
}
As the same OP say in a comment, the difference in the signature between the two gmna_helper()
, the int
vs the long
argument, is to introduce a ordering: the function is called with a int
so the version receiving exactly an int
is selected, when available. That is: is selected when a functional of type F
is callable whith only the first sizeof...(Is)
arguments.
Otherwise, when the int
version isn't available (SFINAE disabled through the failure in the call inside the decltype()
) the long
(ever available) is selected.
Upvotes: 3