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:
Try getting its
operator()
address. If it fails, then the object may either be non-callable or itsoperator()
could be overloaded/templated.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.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
Do These Members Have Unspecified Ordering
Detect Gcc Compile-Time Flags of a Binary
Why Are Forward Declarations Necessary
Is There a Limit of Stack Size of a Process in Linux
What Is the Use of 0-Length Array (Or Std::Array)
How Are Local and Global Variables Initialized by Default
What Does "Void *(*)(Void *)" Mean in C++
Diamond Inheritance Lowest Base Class Constructor
Passing Variable Number of Arguments with Different Type - C++
Sine Wave That Slowly Ramps Up Frequency from F1 to F2 for a Given Time
Cpu Dispatcher for Visual Studio for Avx and Sse
Somehow Register My Classes in a List
Arithmetic Right Shift Gives Bogus Result
If I Want to Specialise Just One Method in a Template, How to Do It