Arity of a Generic Lambda

Arity of a generic lambda

It's impossible, as the function call operator can be a variadic template. It's been impossible to do this forever for function objects in general, and special-casing lambdas because they happened to not be equally powerful was always going to be a bad idea. Now it's just time for that bad idea to come home to roost.

Specializing function template based on lambda arity

I would use different overloads:

template<typename Function>
auto higherOrderFun(Function&& func)
-> decltype(std::forward<Function>(func)(1, 2, 3))
{
return std::forward<Function>(func)(1, 2, 3);
}

template<typename Function>
auto higherOrderFun(Function&& func)
-> decltype(std::forward<Function>(func)(1, 2))
{
return std::forward<Function>(func)(1, 2);
}

Possibly with overload priority as

 struct low_priority {};
struct high_priority : low_priority{};

template<typename Function>
auto higherOrderFunImpl(Function&& func, low_priority)
-> decltype(std::forward<Function>(func)(1, 2))
{
return std::forward<Function>(func)(1, 2);
}

template<typename Function>
auto higherOrderFunImpl(Function&& func, high_priority)
-> decltype(std::forward<Function>(func)(1, 2))
{
return std::forward<Function>(func)(1, 2);
}

template<typename Function>
auto higherOrderFun(Function&& func)
-> decltype(higherOrderFun(std::forward<Function>(func), high_priority{}))
{
return higherOrderFun(std::forward<Function>(func), high_priority{});
}

If you want to use the arity traits from florestan, it might result in:

template<typename F>
decltype(auto) higherOrderFun(F&& func)
{
if constexpr (arity_v<std::decay_t<F>, MaxArity> == 3)
{
return std::forward<F>(func)(1, 2, 3);
}
else if constexpr (arity_v<std::decay_t<F>, MaxArity> == 2)
{
return std::forward<F>(func)(1, 2);
}
// ...
}

Check a type is a functor including generic lambda

There is no proper way of doing this (at least until we get static reflection). The best you can do is check that an object is callable with a certain degree of confidence:

  1. Try getting its operator() address. If it fails, then the object may either be non-callable or its operator() could be overloaded/templated.

  2. Try calling the object with a dummy any_type instance that provides an interface for commonly used function. This might help you deduce its arity.

  3. If everything fails, force the user to somehow help the arity deduction or manually specify the arity.

One way you could approach this is by having a deduced_arity set of tags:

namespace deduced_arity
{
template <std::size_t TS>
struct deducible_t : std::integral_constant<std::size_t, TS>
{
};

struct undeducible_t
{
};

constexpr undeducible_t undeducible{};
constexpr deducible_t<1> unary{};
}

You will also need some sort of function_traits implementation that will statically tell you the exact arity of a function object. This can be found in Boost.

Then you also need an implementation of any_type.

Afterwards, you can use something like the following type trait to check whether or not a function object may be overloaded, using the detection idiom:

template <typename T>
using is_not_overloaded_impl = decltype(&T::operator());

template <typename T>
using is_not_overloaded =
std::experimental::is_detected<is_not_overloaded_impl, T>;

Then you can use a chain of if constexpr(...) (or any other compile-time branching mechanism) to make a good guess - example:

if constexpr(is_not_overloaded<T>{})
{
// use `function_traits` here
}
else if constexpr(std::is_callable<T(any_type)>{})
{
return deduced_arity::unary;
}
else if constexpr(/* user manually marked arity */)
{
/* deal with user-defined deduction helpers */
}
else
{
return deduced_arity::undeducible;
}

Get function arity from template parameter

Assuming that all the operator()'s and functions we're talking about are not templates or overloaded:

template <typename T>
struct get_arity : get_arity<decltype(&T::operator())> {};
template <typename R, typename... Args>
struct get_arity<R(*)(Args...)> : std::integral_constant<unsigned, sizeof...(Args)> {};
// Possibly add specialization for variadic functions
// Member functions:
template <typename R, typename C, typename... Args>
struct get_arity<R(C::*)(Args...)> :
std::integral_constant<unsigned, sizeof...(Args)> {};
template <typename R, typename C, typename... Args>
struct get_arity<R(C::*)(Args...) const> :
std::integral_constant<unsigned, sizeof...(Args)> {};

// Add all combinations of variadic/non-variadic, cv-qualifiers and ref-qualifiers

Demo.

How can I find the number of arguments a template function takes?

I guess I achieved half of a solution here. Only works up to a fixed number of parameters, but for most applications that shouldn't be an issue. Also, it's probably highly simplifiable but my brain is not into tricky SFINAE right now.

template <
class, std::size_t N,
class = std::make_index_sequence<N>,
class = void_t<>
>
struct CanCall : std::false_type { };

template <class F, std::size_t N, std::size_t... Idx>
struct CanCall<
F, N,
std::index_sequence<Idx...>,
void_t<decltype(std::declval<F>()((Idx, std::declval<Any const&&>())...))>
> : std::true_type { };

CanCall<F, N> will return whether F is callable with N parameters of arbitrary type. The Any helper type has templated implicit conversion operators that allows it to morph into any desired parameter type.

template <class F, std::size_t N = 0u, class = void>
struct Arity : Arity<F, N + 1u, void> { };

template <class F, std::size_t N>
struct Arity<F, N, std::enable_if_t<CanCall<F, N>::value>>
: std::integral_constant<std::size_t, N> { };

template <class F>
struct Arity<F, MAX_ARITY_PROBING, void>
: std::integral_constant<std::size_t, ARITY_VARIADIC> { };

Arity<F> just checks whether an F can be called with zero, one, two... parameters. First positive check wins. If we reach MAX_ARITY_PROBING parameters, Arity bails out and supposes that the function is either variadic, or is not a function at all.

See it live on Coliru

Function in Scheme / Racket returning functions of particular arities

This feels like an XY problem, so I don't know if this well help you, but:

As @Renzo commented, if you do not need to do this at run time, it might be cleaner and faster to use a macro to do it at compile time.

I don't understand why you need a get-function-variadic that returns functions that are... not variadic. However I suppose you could use procedure-reduce-arity to get the expected results in your examples:

#lang racket

(define (-get-function-variadic n)
(lambda arguments
(if (empty? arguments) 0
(+ (car arguments)
(apply (get-function-variadic (sub1 n)) (cdr arguments))))))

(define (get-function-variadic n)
(procedure-reduce-arity (-get-function-variadic n) n))

(require rackunit)

(check-exn exn:fail:contract:arity? (λ () ((get-function-variadic 3) 1 2)))
(check-equal? ((get-function-variadic 3) 1 2 3) 6)
(check-exn exn:fail:contract:arity? (λ () ((get-function-variadic 3) 1 2 3 4)))

C++ Lambdas with Ellipses in the Parameter List

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.

Is it possible to figure out the parameter type and return type of a lambda?

Funny, I've just written a function_traits implementation based on Specializing a template on a lambda in C++0x which can give the parameter types. The trick, as described in the answer in that question, is to use the decltype of the lambda's operator().

template <typename T>
struct function_traits
: public function_traits<decltype(&T::operator())>
{};
// For generic types, directly use the result of the signature of its 'operator()'

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const>
// we specialize for pointers to member function
{
enum { arity = sizeof...(Args) };
// arity is the number of arguments.

typedef ReturnType result_type;

template <size_t i>
struct arg
{
typedef typename std::tuple_element<i, std::tuple<Args...>>::type type;
// the i-th argument is equivalent to the i-th tuple element of a tuple
// composed of those arguments.
};
};

// test code below:
int main()
{
auto lambda = [](int i) { return long(i*10); };

typedef function_traits<decltype(lambda)> traits;

static_assert(std::is_same<long, traits::result_type>::value, "err");
static_assert(std::is_same<int, traits::arg<0>::type>::value, "err");

return 0;
}

Note that this solution does not work for generic lambda like [](auto x) {}.



Related Topics



Leave a reply



Submit