Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ Lambdas with Ellipses in the Parameter List

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?

like image 763
Dennis Avatar asked May 10 '18 20:05

Dennis


Video Answer


2 Answers

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
 }
like image 169
max66 Avatar answered Sep 23 '22 07:09

max66


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.

like image 42
hlt Avatar answered Sep 26 '22 07:09

hlt