Arity универсальной лямбды

Можно вывести арентность неуниверсальной лямбды, открыв operator(),

template <typename F>
struct fInfo : fInfo<decltype(&F::operator())> { };

template <typename F, typename Ret, typename... Args>
struct fInfo<Ret(F::*)(Args...)const> { static const int arity = sizeof...(Args); };

Это мило и модно для чего-то вроде [](int x){ return x; } как operator() не является шаблонным

Тем не менее, общие лямбды делают шаблон operator() и только возможно получить доступ к конкретному экземпляру шаблона — что немного проблематично, потому что я не могу вручную предоставить аргументы шаблона для operator() поскольку я не знаю, в чем его сила.

Так что, конечно, что-то вроде

auto lambda = [](auto x){ return x; };
auto arity = fInfo<decltype(lambda)>::arity;

не работает

Я не знаю, к чему приводить, и при этом я не знаю, какие аргументы шаблона предоставить (или сколько) (operator()<??>).
Есть идеи, как это сделать?

8

Решение

Это невозможно, так как оператор вызова функции может быть вариационным шаблоном. Это было невозможно сделать навсегда для функциональных объектов в целом, и лямбды в специальном корпусе, потому что они оказывались не такими же мощными, всегда были плохой идеей. Теперь пришло время для этой плохой идеи вернуться домой к насесту.

2

Другие решения

Эта техника будет работать в некоторых случаях. Я создаю fake_anything тип, который может подделать почти все, и попытаться вызвать вашу лямбду с некоторым количеством экземпляров этого.

#include <iostream>

struct fake_anything {
fake_anything(fake_anything const&);
fake_anything();
fake_anything&operator=(fake_anything const&);
template<class T>operator T&() const;
template<class T>operator T&&() const;
template<class T>operator T const&() const;
template<class T>operator T const&&() const;
fake_anything operator*() const;
fake_anything operator++() const;
fake_anything operator++(int) const;
fake_anything operator->() const;
template<class T>fake_anything(T&&);
};
fake_anything operator+(fake_anything, fake_anything);
fake_anything operator-(fake_anything, fake_anything);
fake_anything operator*(fake_anything, fake_anything);
fake_anything operator/(fake_anything, fake_anything);
// etc for every operator

template<class>using void_t=void;
template<class Sig, class=void>
struct can_invoke:std::false_type{};
template<class F, class...Args>
struct can_invoke<F(Args...),
void_t< decltype( std::declval<F>()( std::declval<Args>()... ) ) >
> : std::true_type
{};

template<class Sig>struct is_sig:std::false_type{};
template<class R, class...Args>struct is_sig<R(Args...)>:std::true_type{};

template<unsigned...>struct indexes{using type=indexes;};
template<unsigned Max,unsigned...Is>struct make_indexes:make_indexes<Max-1,Max-1,Is...>{};
template<unsigned...Is>struct make_indexes<0,Is...>:indexes<Is...>{};
template<unsigned max>using make_indexes_t=typename make_indexes<max>::type;

template<class T,unsigned>using unpacker=T;

template<class F, class A, class indexes>
struct nary_help;
template<class F, class A, unsigned...Is>
struct nary_help<F,A,indexes<Is...>>:
can_invoke<F( unpacker<A,Is>... )>
{};
template<class F, unsigned N>
struct has_n_arity:
nary_help<F, fake_anything, make_indexes_t<N>>
{};

template<class F, unsigned Min=0, unsigned Max=10>
struct max_arity{
enum{Mid=(Max+Min)/2};
enum{
lhs = max_arity<F,Min,Mid>::value,
rhs = max_arity<F,Mid+1,Max>::value,
value = lhs>rhs?lhs:rhs,
};
};
template<class F, unsigned X>
struct max_arity<F,X,X>:
std::integral_constant<int, has_n_arity<F,X>::value?(int)X:-1>
{};

template<class F, unsigned Min=0, unsigned Max=10>
struct min_arity{
enum{Mid=(Max+Min)/2};
enum{
lhs = min_arity<F,Min,Mid>::value,
rhs = min_arity<F,Mid+1,Max>::value,
value = lhs<rhs?lhs:rhs,
};
};
template<class F, unsigned X>
struct min_arity<F,X,X>:
std::integral_constant<unsigned,has_n_arity<F,X>::value?X:(unsigned)-1>
{};

auto test1 = [](auto x, auto y)->bool { return x < y; };
auto test2 = [](auto x, auto y) { return x + y; };
auto test3 = [](auto x) { return x.y; };

int main() {
std::cout << can_invoke< decltype(test1)( fake_anything, fake_anything ) >::value << "\n";
std::cout << can_invoke< decltype(test1)( int, int ) >::value << "\n";
std::cout << has_n_arity< decltype(test1), 2 >::value << "\n";
std::cout << max_arity< decltype(test1) >::value << "\n";
std::cout << max_arity< decltype(test2) >::value << "\n";
// will fail to compile:
// std::cout << max_arity< decltype(test3) >::value << "\n";
}

живой пример.

Обратите внимание, что достаточное количество SFINAE будет означать, что вышеупомянутое получит неправильный результат, так как будет использовать operator.или использование operator. на определенные виды «производных» типов или доступ к типам на основе fake_anything параметр и т. д.

Однако, если лямбда-выражение указывает свое возвращаемое значение с ->X оговорка, то fake_anything более чем достаточно. Трудная часть имеет дело с телом.

Обратите внимание, что этот подход часто является плохой идеей, потому что, если вы хотите узнать арность функции, вы, вероятно, также знаете типы вещей, с которыми вы хотите вызвать объект функции! И выше я действительно легко отвечаю на этот вопрос (может ли этот функциональный объект вызываться с этими аргументами?). Можно даже улучшить, спросив «какой самый длинный / самый короткий префикс этих аргументов, который может вызвать этот функциональный объект», или обработать «сколько повторений типа X работает, чтобы вызвать этот функциональный объект» (если вы хотите чистый сбой, вы нужна верхняя граница).

6

Я бы сказал, что это частично возможно, по крайней мере, вы можете знать общая арность (шаблонные + обычные типы), когда вы явно создаете автоматические параметры operator():

template <typename F, typename... Args>
struct autofInfo : fInfo<decltype(&F::template operator()<Args...>)> {};

auto lambda = [](auto x, int y, float z) { return x + y + z; };

auto arity = autofInfo<decltype(lambda), int>::arity;
//                                       ^^^ list of auto parameters instantiations

assert(3 == arity);
1

Это решение c ++ 17, которое работает с универсальными и переменными лямбдами и функторами с оператором variadic templatet (). идея состоит в том, чтобы рекурсивно смоделировать вызов с уменьшающимся числом аргументов и использовать SFINAE, чтобы прервать рекурсию, когда найдено первое совпадающее число аргументов. Это компиляторы на gcc> = 7 и Clang> = 5. Рабочий пример можно найти Вот.

#include<utility>

constexpr size_t max_arity = 10;

struct variadic_t
{
};

namespace detail
{
// it is templated, to be able to create a
// "sequence" of arbitrary_t's of given size and
// hece, to 'simulate' an arbitrary function signature.
template <size_t>
struct arbitrary_t
{
// this type casts implicitly to anything,
// thus, it can represent an arbitrary type.
template <typename T>
operator T &&();

template <typename T>
operator T &();
};

template <typename F, size_t... Is,
typename U = decltype(std::declval<F>()(arbitrary_t<Is>{}...))>
constexpr auto test_signature(std::index_sequence<Is...>)
{
return std::integral_constant<size_t, sizeof...(Is)>{};
}

template <size_t I, typename F>
constexpr auto arity_impl(int) -> decltype(test_signature<F>(std::make_index_sequence<I>{}))
{
return {};
}

template <size_t I, typename F, typename = std::enable_if_t<(I > 0)>>
constexpr auto arity_impl(...)
{
// try the int overload which will only work,
// if F takes I-1 arguments. Otherwise this
// overload will be selected and we'll try it
// with one element less.
return arity_impl<I - 1, F>(0);
}

template <typename F, size_t MaxArity = 10>
constexpr auto arity_impl()
{
// start checking function signatures with max_arity + 1 elements
constexpr auto tmp = arity_impl<MaxArity + 1, F>(0);
if constexpr (tmp == MaxArity + 1)
{
// if that works, F is considered variadic
return variadic_t{};
}
else
{
// if not, tmp will be the correct arity of F
return tmp;
}
}
}

template <typename F, size_t MaxArity = max_arity>
constexpr auto arity(F&& f) { return detail::arity_impl<std::decay_t<F>, MaxArity>(); }

template <typename F, size_t MaxArity = max_arity>
constexpr auto arity_v = detail::arity_impl<std::decay_t<F>, MaxArity>();

template <typename F, size_t MaxArity = max_arity>
constexpr bool is_variadic_v = std::is_same_v<std::decay_t<decltype(arity_v<F, MaxArity>)>, variadic_t>;

Использование:

auto l = [](auto...){};
static_assert(is_variadic_v<decltype(l)>);

а также:

auto l = [](auto, auto, auto){};
static_assert(!is_variadic_v<decltype(l)>);
static_assert(arity(l) == 3);
1

Другое возможное решение для случая, когда известны возможные типы шаблонов:
http://coliru.stacked-crooked.com/a/e3a07d723a8f27e9

using T1 = string;
using T2 = int;

std::integral_constant<int, 1> static arity(function<void(T1)>){ return {}; }
std::integral_constant<int, 2> static arity(function<void(T1, T2)>){ return {}; }

template<class Fn>
using Arity = decltype(arity(Fn{}));
0
По вопросам рекламы [email protected]