J'ai besoin de connaître le nombre exact d'arguments d'un lambda. Je me fiche de leurs types, j'ai juste besoin d'un décompte.

auto lambda0 = [&]() { ... };
auto lambda1 = [&](int32_t a) { ... };
auto lambda2 = [&](int32_t a, auto b) { ... };

lambda_details<decltype(lambda0)>::argument_count; // Equals 0
lambda_details<decltype(lambda1)>::argument_count; // Equals 1
lambda_details<decltype(lambda2)>::argument_count; // Equals 2

Détecter les lambdas variadiques serait également bien pour que je puisse également traiter ce cas de bord.

auto lambda_variadic = [&](auto... args){ ... };

lambda_details<decltype(lambda_variadic)>::is_variadic; // Equals true

Comment puis-je obtenir ces informations?

14
Rick de Water 27 janv. 2019 à 18:38

3 réponses

Meilleure réponse

Je l'ai résolu en utilisant une version modifiée de la réponse de @yuri kilochek.

Au lieu de partir de 50 arguments et de compter à rebours, nous commençons à zéro et comptons à rebours. Lorsque nous obtenons une correspondance, nous connaissons le nombre minimum d'arguments requis pour appeler le lambda. Nous continuons ensuite à chercher jusqu'à un maximum raisonnable pour voir s'il y a un nombre maximum d'arguments (cela peut arriver lorsque vous avez des arguments par défaut).

Si la limite du nombre d'arguments est atteinte, nous supposons que le lambda est variadique.

Cette implémentation réduit considérablement la quantité d'instanciations de modèle pour les lambdas non variadiques. Il nous donne également le nombre minimum d'arguments pour tous les lambdas, et le nombre maximum d'arguments pour tous les lambdas non variadiques.

Encore une fois, un grand merci à Yuri Kilochek pour avoir posé les bases de cette solution élégante. Vérifiez sa réponse pour plus de détails sur la mise en œuvre.

struct any_argument
{
    template <typename T>
    operator T && () const;
};

template <typename Lambda, typename Is, typename = void>
struct can_accept_impl : std::false_type
{};

template <typename Lambda, std::size_t ...Is>
struct can_accept_impl <Lambda, std::index_sequence<Is...>, decltype(std::declval<Lambda>()(((void)Is, any_argument{})...), void())> : std::true_type
{};

template <typename Lambda, std::size_t N>
struct can_accept : can_accept_impl<Lambda, std::make_index_sequence<N>>
{};

template <typename Lambda, std::size_t N, size_t Max, typename = void>
struct lambda_details_maximum
{
    static constexpr size_t maximum_argument_count = N - 1;
    static constexpr bool is_variadic = false;
};

template <typename Lambda, std::size_t N, size_t Max>
struct lambda_details_maximum<Lambda, N, Max, std::enable_if_t<can_accept<Lambda, N>::value && (N <= Max)>> : lambda_details_maximum<Lambda, N + 1, Max>
{};

template <typename Lambda, std::size_t N, size_t Max>
struct lambda_details_maximum<Lambda, N, Max, std::enable_if_t<can_accept<Lambda, N>::value && (N > Max)>>
{
    static constexpr bool is_variadic = true;
};

template <typename Lambda, std::size_t N, size_t Max, typename = void>
struct lambda_details_minimum : lambda_details_minimum<Lambda, N + 1, Max>
{
    static_assert(N <= Max, "Argument limit reached");
};

template <typename Lambda, std::size_t N, size_t Max>
struct lambda_details_minimum<Lambda, N, Max, std::enable_if_t<can_accept<Lambda, N>::value>> : lambda_details_maximum<Lambda, N, Max>
{
    static constexpr size_t minimum_argument_count = N;
};

template <typename Lambda, size_t Max = 50>
struct lambda_details : lambda_details_minimum<Lambda, 0, Max>
{};

Une autre chose importante à noter est que any_argument ne joue pas automatiquement bien avec les opérateurs. Vous devrez surcharger chacun d'entre eux si vous voulez qu'il fonctionne avec des arguments auto qui sont exploités (par exemple [](auto a) { return a * 2; }). Cela finira par ressembler davantage à ceci:

struct any_argument
{
    template <typename T> operator T && () const;

    any_argument& operator ++();
    any_argument& operator ++(int);
    any_argument& operator --();
    any_argument& operator --(int);

    template <typename T> friend any_argument operator + (const any_argument&, const T&);
    template <typename T> friend any_argument operator + (const T&, const any_argument&);
    template <typename T> friend any_argument operator - (const any_argument&, const T&);
    template <typename T> friend any_argument operator - (const T&, const any_argument&);
    template <typename T> friend any_argument operator * (const any_argument&, const T&);
    template <typename T> friend any_argument operator * (const T&, const any_argument&);
    template <typename T> friend any_argument operator / (const any_argument&, const T&);
    template <typename T> friend any_argument operator / (const T&, const any_argument&);

    // And every other operator in existence
};
3
Rick de Water 28 janv. 2019 à 16:34

Vous pouvez créer un objet qui peut entrer dans n'importe quel paramètre en surchargeant l'opérateur de conversion. À partir de là, testez simplement si le lambda peut être appelé avec un nombre donné de tels arguments, en comptant à rebours à partir d'un grand nombre arbitraire. Si le lambda se trouve être appelé au premier essai (avec un grand nombre d'arguments arbitraire donné), nous pouvons supposer qu'il est variadique:

#include <iostream>
#include <utility>
#include <type_traits>


struct any_argument {
    template <typename T>
    operator T&&() const;
};


template <typename Lambda, typename Is, typename = void>
struct can_accept_impl
: std::false_type
{};

template <typename Lambda, std::size_t ...Is>
struct can_accept_impl<Lambda, std::index_sequence<Is...>, 
                       decltype(std::declval<Lambda>()(((void)Is, any_argument{})...), void())>
: std::true_type
{};

template <typename Lambda, std::size_t N>
struct can_accept
: can_accept_impl<Lambda, std::make_index_sequence<N>>
{};


template <typename Lambda, std::size_t Max, std::size_t N, typename = void>
struct lambda_details_impl
: lambda_details_impl<Lambda, Max, N - 1>
{};

template <typename Lambda, std::size_t Max, std::size_t N>
struct lambda_details_impl<Lambda, Max, N, std::enable_if_t<can_accept<Lambda, N>::value>>
{
    static constexpr bool is_variadic = (N == Max);
    static constexpr std::size_t argument_count = N;
};

template <typename Lambda, std::size_t Max = 50>
struct lambda_details
: lambda_details_impl<Lambda, Max, Max>
{};


int main()
{
    auto lambda0 = []() {};
    auto lambda1 = [](int a) {};
    auto lambda2 = [](int a, auto b) {};
    auto lambda3 = [](int a, auto b, char = 'a') {};
    auto lambda4 = [](int a, auto b, char = 'a', auto...) {};

    std::cout << lambda_details<decltype(lambda0)>::is_variadic << " " << lambda_details<decltype(lambda0)>::argument_count << "\n"; // 0 0
    std::cout << lambda_details<decltype(lambda1)>::is_variadic << " " << lambda_details<decltype(lambda1)>::argument_count << "\n"; // 0 1
    std::cout << lambda_details<decltype(lambda2)>::is_variadic << " " << lambda_details<decltype(lambda2)>::argument_count << "\n"; // 0 2
    std::cout << lambda_details<decltype(lambda3)>::is_variadic << " " << lambda_details<decltype(lambda3)>::argument_count << "\n"; // 0 3
    std::cout << lambda_details<decltype(lambda4)>::is_variadic << " " << lambda_details<decltype(lambda4)>::argument_count << "\n"; // 1 50
}
7
yuri kilochek 27 janv. 2019 à 18:52

Je ne sais pas comment compter tous les arguments d'un lambda générique [ modifier : mais yuri kilochek sait comment le faire: voyez sa réponse pour une excellente solution].

Pour les lambdas non génériques, comme suggéré par Igor Tandetnik, vous pouvez détecter les types (retour et arguments) du pointeur vers operator() et compter les arguments.

Quelque chose comme suit

// count arguments helper
template <typename R, typename T, typename ... Args>
constexpr std::size_t  cah (R(T::*)(Args...) const)
 { return sizeof...(Args); }

// count arguments helper
template <typename R, typename T, typename ... Args>
constexpr std::size_t  cah (R(T::*)(Args...))
 { return sizeof...(Args); }

template <typename L>
constexpr auto countArguments (L)
 { return cah(&L::operator()); }

Mais, malheureusement, cela ne fonctionne pas lorsque vous introduisez un argument auto car, avec un argument auto, vous transformez operator() en une fonction modèle.

À propos de la détection d'un lambda variadique, vous pouvez détecter une fonction avec seulement une liste variadique d'arguments (laissez-moi l'appeler "pure variadic"), comme votre lambda_variadic, en essayant de l'appeler avec zéro et avec (par exemple) 50 argument d'un type donné.

Je veux dire quelque chose comme suit

template <typename T, std::size_t>
struct getType
 { using type = T; };

template <typename T, std::size_t N>
using getType_t = typename getType<T, N>::type;

// isPureVariadic arguments helper
template <typename T>
constexpr std::false_type ipvh (...);

// isPureVariadic arguments helper
template <typename T, typename F, std::size_t ... Is>
constexpr auto ipvh (F f, std::index_sequence<Is...>)
   -> decltype( f(std::declval<getType_t<T, Is>>()...), std::true_type{} );

template <typename F>
constexpr bool isPureVariadic (F f)
 { return
      decltype(ipvh<int>(f, std::make_index_sequence<0u>{}))::value
   && decltype(ipvh<int>(f, std::make_index_sequence<50u>{}))::value; }

Mais ce n'est pas parfait car donne des faux positifs et des faux négatifs.

Un problème est que lorsque vous le vérifiez avec un "lambda variadique non pur" comme

 auto lambda_variadic2 = [&](std::string, auto... args){ ... };

C'est variadique mais le premier argument n'accepte pas un int, n'est pas détecté comme "pur variadique"; malheureusement le lambda suivant

 auto lambda_variadic3 = [&](long, auto... args){ ... };

Est détecté comme "pur variadique" car le premier argument accepte un int.

Pour éviter ce problème, vous pouvez modifier la fonction pour vérifier l'appel avec 50 arguments de deux types incompatibles; par exemple

template <typename F>
constexpr bool isPureVariadic (F f)
 { return
      decltype(ipvh<int>(f, std::make_index_sequence<0u>{}))::value
   && decltype(ipvh<int>(f, std::make_index_sequence<50u>{}))::value
   && decltype(ipvh<std::string>(f, std::make_index_sequence<50u>{}))::value; }

Un autre problème est que les fonctions lambda génériques non variadiques sont détectées comme "purement virtuelles" recevant un nombre d'arguments supérieur au nombre vérifié (50, dans l'exemple).

Et reste le problème que cette solution ne détecte pas lambda_variadic2 (un lambda variadique non pur) comme variadique.

Ce qui suit est un exemple de compilation complet avec le meilleur que je puisse imaginer à propos de votre question

#include <iostream>
#include <utility>
#include <type_traits>

// count arguments helper
template <typename R, typename T, typename ... Args>
constexpr std::size_t  cah (R(T::*)(Args...) const)
 { return sizeof...(Args); }

// count arguments helper
template <typename R, typename T, typename ... Args>
constexpr std::size_t  cah (R(T::*)(Args...))
 { return sizeof...(Args); }

template <typename L>
constexpr auto countArguments (L)
 { return cah(&L::operator()); }

template <typename T, std::size_t>
struct getType
 { using type = T; };

template <typename T, std::size_t N>
using getType_t = typename getType<T, N>::type;

// isPureVariadic arguments helper
template <typename T>
constexpr std::false_type ipvh (...);

// isPureVariadic arguments helper
template <typename T, typename F, std::size_t ... Is>
constexpr auto ipvh (F f, std::index_sequence<Is...>)
   -> decltype( f(std::declval<getType_t<T, Is>>()...), std::true_type{} );

template <typename F>
constexpr bool isPureVariadic (F f)
 { return
      decltype(ipvh<int>(f, std::make_index_sequence<0u>{}))::value
   && decltype(ipvh<int>(f, std::make_index_sequence<50u>{}))::value; }


int main() {
   auto lambda0 = [&]() {};
   auto lambda1 = [&](int) {};
   auto lambda2 = [&](int, auto) {};
   auto lambda3 = [&](auto...) {};

   std::cout << countArguments(lambda0) << std::endl;
   std::cout << countArguments(lambda1) << std::endl;
   // std::cout << countArguments(lambda2) << std::endl; // compilation error
   // std::cout << countArguments(lambda3) << std::endl; // compilation error

   std::cout << isPureVariadic(lambda0) << std::endl;
   std::cout << isPureVariadic(lambda1) << std::endl;
   std::cout << isPureVariadic(lambda2) << std::endl;
   std::cout << isPureVariadic(lambda3) << std::endl;
}
2
max66 28 janv. 2019 à 13:15