yultan
yultan

Reputation: 239

Call with minimal number of argument from lambda with default arguments

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

Answers (1)

max66
max66

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

Related Questions