6

I'm working on a library which uses lambdas for delineating the scopes of expression terms. Because the library has to hand out unique integer numbers to identify each variable, it is ideal if the library, not the user, constructs the variables and the user code receives them as lambda arguments.

(In other words I am implementing a C++ analog of "call\fresh" from miniKanren.)

Since the user may want to introduce any number from zero to many fresh variables at a particular scope, I want the user to be able to pass lambdas with differing numbers of arguments to the library. However, I'm not aware of any (simple) way (in C++14) to deduce the number of parameters to an arbitrary lambda object.

An idea occurred to me why not pass a fixed number (say, 10) of variable-id arguments to the lambda, and have the user code use ellipses in the lambda to ignore the ones not needed? Something like this:

auto no_args = call_fresh([](...) { return success(); });
auto one_arg = call_fresh([](var A, ...) { return A == 1; });
auto two_args = call_fresh([](var A, var B, ...) { return A == 1 && B == 2; });

Compiler explorer seems to accept ellipses in lambda parameter lists, at least with gcc.

It would be called something like this (note how the code always passes 10 variable id's no matter whether "f" names only one, two, or none of them):

template <typename F>
auto call_fresh(F f)
{
   return [f](StateCounter sc) {
      return f(sc+0,sc+1,sc+2,sc+3,sc+4,
          sc+5,sc+6,sc+7,sc+8,sc+9);
   };
}

Granted it's a feature I was surprised exists, is there any reason not to use lambdas with ellipses?

max66
  • 65,235
  • 10
  • 71
  • 111
Dennis
  • 2,607
  • 3
  • 21
  • 28
  • 2
    Are [variadic templates](http://en.cppreference.com/w/cpp/language/parameter_pack) or [initializer lists](http://en.cppreference.com/w/cpp/utility/initializer_list) maybe what you are looking for? – Jesper Juhl May 10 '18 at 20:11
  • While a variadic template might simplify an argument-number detector, it doesn't solve the problem that (as far as I know) such a detector currently only works for lambdas without captures (because only they have an operator that decays them to function pointers). As for initializer lists, they suffer from the problem that all of the arguments must have the same type, and I want to ultimately have more than one type of "var". – Dennis May 10 '18 at 20:20

2 Answers2

5

However, I'm not aware of any (simple) way (in C++14) to deduce the number of parameters to an arbitrary lambda object.

It seems to me that you're looking for sizeof...() over a variadic auto list of paramenters

#include <iostream>

int main ()
 {
   auto l = [](auto ... as) { return sizeof...(as); };

   std::cout << l(1, 2L, 3.0, 4.0f, "5") << std::endl; // print 5
 }
max66
  • 65,235
  • 10
  • 71
  • 111
  • Upvoted because I learned something new (I didn't know about the auto ... syntax), but this is actually the reverse of what I need. I don't need to know the number of parameters from inside the lambda, I need to know the number of parameters from the invoker of the lambda. – Dennis May 10 '18 at 20:27
  • In other words, the goal is for the lambda to tell the invoking code how many it needs, not for the invoking code to tell the lambda how many it got. Passing a fixed number was just a work-around for not having reflection that would allow the library to inspect the user's lambda object. – Dennis May 10 '18 at 20:29
  • @Dennis - not sure to understand... is good for you a type-traits (or a constexpr function, or something similar) that, given a lambda, return the number of parameters it needs? And the types are known or can be different? Sorry but from your question isn't clear, for me, what do you want. – max66 May 10 '18 at 20:36
  • the code in hlt 's answer answers your question, and mine. If my question was confusing it may be because it was in the form of "given that I can't do this, is this a good work-around?" and hlt 's answer was to invalidate the premise of my question: actually it is possible to do what I really need so the work-around is not necessary. – Dennis May 10 '18 at 20:43
4

Your lambdas are essentially C-style variadic functions. There's nothing wrong with using them, and if you don't want to access the values (which is somewhat ugly), that is fine.

However, the underlying problem that it seems like you actually want to solve is to let your library find the number of arguments (or arity) of a function/lambda/..., which you can do with template metaprogramming - no need for your users to work around that issue.

Disclosure: There is an implementation of this in a library that I also work on, here.

Here is a simple example:

template <typename Callable>
struct function_arity : public function_arity<decltype(&Callable::operator())>
{};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_arity<ReturnType(ClassType::*)(Args...) const>
{
    constexpr static size_t arity = sizeof...(Args);
};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_arity<ReturnType(ClassType::*)(Args...)>
{
    constexpr static size_t arity = sizeof...(Args);
};

The compiler will automatically deduce the argument types for you, and sizeof... will get you the number of arguments that you need.

Then, you can use function_arity<decltype(lambda)>::arity to get the number of arguments of your lambda. The last version deals with mutable lambdas, where the call operator is non-constant. You may also want to extend this to work properly with noexcept, or you will run into errors like this libc++ bug.

Unfortunately, this will not work with overloaded or templated operator() (e.g. if you use auto-type parameters in your lambda). If you also want to support functions instead of lambdas, additional specializations may be necessary.

hlt
  • 6,219
  • 3
  • 23
  • 43