Arität eines generischen Lambda

Lesezeit: 10 Minuten

Aritat eines generischen Lambda
Scharlachroter Amaranth

Es ist möglich, die Arität eines nicht generischen Lambda abzuleiten, indem man darauf zugreift 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); };

Das ist schön und gut für so etwas [](int x){ return x; } als die operator() ist nicht schablonenhaft.

Generische Lambdas erstellen jedoch Vorlagen für die operator() und es ist nur möglich, auf eine konkrete Instanziierung der Vorlage zuzugreifen – was etwas problematisch ist, da ich Vorlagenargumente für die nicht manuell bereitstellen kann operator() da ich nicht weiß, was seine Arität ist.

Also natürlich so etwas wie

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

funktioniert nicht.

Ich weiß weder, wohin ich umwandeln soll, noch weiß ich, welche Vorlagenargumente ich bereitstellen soll (oder wie viele) (operator()<??>).
Irgendwelche Ideen, wie man das macht?

  • Der operator() von generischen Lambdas können variadische Vorlagen sein. Was ist Ihre Definition von Arität für diese?

    – TC

    6. September 14 um 20:13 Uhr

  • Warum willst du Arity fragen und nicht „Wie viele? ints kannst du akzeptieren?’?

    – Yakk – Adam Nevraumont

    6. September 14 um 22:42 Uhr

1642600688 610 Aritat eines generischen Lambda
Yakk – Adam Nevraumont

Diese Technik wird in einigen Fällen funktionieren. Ich erstelle eine fake_anything Typ, der fast alles vortäuschen kann, und versuchen Sie, Ihr Lambda mit einigen Instanzen davon aufzurufen.

#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";
}

Live-Beispiel.

Beachten Sie, dass ausreichend SFINAE bedeutet, dass das obige Ergebnis ein falsches Ergebnis liefert, ebenso wie die Verwendung von operator., oder Verwendung von operator. auf bestimmte Arten von “abgeleiteten” Typen oder auf Typen basierend auf der fake_anything Parameter usw.

Wenn das Lambda jedoch seinen Rückgabewert mit a angibt ->X Klausel, dann fake_anything ist mehr als gut genug. Der schwierige Teil ist der Umgang mit dem Körper.

Beachten Sie, dass dieser Ansatz oft eine schlechte Idee ist, denn wenn Sie die Stelligkeit einer Funktion wissen möchten, kennen Sie wahrscheinlich auch die Typen der Dinge, mit denen Sie das Funktionsobjekt aufrufen möchten! Und oben beantworte ich diese Frage ganz einfach (kann dieses Funktionsobjekt mit diesen Argumenten aufgerufen werden?). Es kann sogar verbessert werden, zu fragen, “was das längste/kürzeste Präfix dieser Argumente ist, das dieses Funktionsobjekt aufrufen kann”, oder zu behandeln, “wie viele Wiederholungen des Typs X funktionieren, um dieses Funktionsobjekt aufzurufen” (wenn Sie einen sauberen Fehler wünschen, müssen Sie brauchen eine Obergrenze).

  • scheint nicht zu funktionieren, wenn Lambdas Körper versucht, das zu nutzen auto Parameter in einem Kontext, der nicht von unterstützt wird fake_anything, z.B return x*2; oder return x.get();; weil der Compiler den Körper in auswertet can_invoke<T>

    – Piotr Skotnicki

    07.09.14 um 09:11 Uhr


  • @piotrs. ah ja, mit körperbasierter Typableitung muss es sein. Mit -> Es sollte keinen Grund dazu geben, aber das ist immer noch scheiße. Selbst wenn ich jeden Operator überschreibe, gibt es immer noch ..

    – Yakk – Adam Nevraumont

    07.09.14 um 09:21 Uhr

  • @piotrs. einige Verbesserungen. Es funktioniert mit x*2, aber nicht x.get() wenn x ist vom Typ auto.

    – Yakk – Adam Nevraumont

    8. September 14 um 18:17 Uhr

  • Ich muss beachten, dass VS2015 in den meisten Fällen falsche Ergebnisse liefert. Hier ist die Ausgabe: 0 0 0 -1 2

    – Turm120

    18. März 16 um 14:24 Uhr

  • @tower120 Verwenden decltype in MSVC2015 ist ein Rezept für Enttäuschung.

    – Yakk – Adam Nevraumont

    18. März 16 um 14:37 Uhr

Es ist unmöglich, da der Funktionsaufrufoperator eine variadische Vorlage sein kann. Für Funktionsobjekte im Allgemeinen war dies schon immer unmöglich, und Lambdas in Sonderfällen zu verwenden, weil sie zufällig nicht gleich leistungsfähig waren, war immer eine schlechte Idee. Jetzt ist es nur Zeit für diese schlechte Idee, nach Hause zu kommen, um sich niederzulassen.

1642600688 282 Aritat eines generischen Lambda
Florestan

Dies ist eine c++17-Lösung, die mit generischen und variadischen Lambdas und Funktoren mit variadischem Templatet-Operator() arbeitet. Die Idee ist, den Aufruf rekursiv mit einer absteigenden Anzahl von Argumenten zu simulieren und SFINAE zu verwenden, um die Rekursion zu unterbrechen, wenn die erste übereinstimmende Anzahl von Argumenten gefunden wird. Es wird auf gcc >= 7 und Clang >=5 kompiliert. Ein funktionierendes Beispiel kann gefunden werden Hier.

#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>;

Verwendung:

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

und:

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

  • 4 Jahre später haben wir einen Workaround, der nur halb so schlecht ist wie der alte Workaround. Was für ein Fortschritt, C++!

    – Scharlachroter Amaranth

    24. März 18 um 9:49 Uhr

  • Dadurch werden die Typen in beliebig_t konvertiert. Wenn Sie also arity_v verwenden möchten, müssen Sie diese in das Lambda zurückwerfen.

    – Glühbirne

    4. Juli 19 um 20:09 Uhr

Ich würde sagen, das ist teilweise möglich, zumindest kann man das wissen Gesamtarität (auf Vorlagen basierende + reguläre Typen), wenn Sie die Auto-Parameter von explizit instanziieren 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);

Eine weitere mögliche Lösung, falls mögliche Vorlagentypen bekannt sind:
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{}));

.

548520cookie-checkArität eines generischen Lambda

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy