4
votes

Déduction de type lors du passage de lambda dans variadic std :: function

J'essaye de récupérer les valeurs d'un tuple de tableaux en utilisant les informations de type sur la fonction utilisée pour les traiter. Cependant, la déduction de type échoue dans ce cas en raison (en partie?) De la nécessité d'utiliser une structure d'identité pour le nom de type de la std :: function . Y a-t-il un moyen de restaurer la déduction ici?

error: cannot convert 'main()::<lambda(comp_a&, comp_c&)>' to 'storage<20, comp_a, comp_b, comp_c>::identity<std::function<void()> >::type' {aka 'std::function<void()>'}

J'ai rencontré this et this , qui semblent similaires, mais je pense que ma situation est différente car j'ai besoin des types variadiques pour accéder à leurs traits lors de la récupération des valeurs souhaitées. Comme dans ces exemples, les paramètres variadiques de la fonction sont traités comme vides:

#include <functional>
#include <iostream>
#include <tuple>

class comp_a
{
public:
    static const size_t id = 0;

    int val = 0;
};

class comp_b
{
public:
    static const size_t id = 1;

    int val = 0;
};

class comp_c
{
public:
    static const size_t id = 2;

    int val = 0;
};

template<size_t size, typename ... Cs>
struct storage
{    
    template<typename T> struct identity { using type = T; };

    template<typename ... Ts>
    void get(size_t index, typename identity<std::function<void(Ts& ...)>>::type f)
    {
        f(std::get<Ts::id>(components)[index] ...);
    }

    std::tuple<std::array<Cs, size> ...> components;
};

int32_t main()
{
    storage<20, comp_a, comp_b, comp_c> storage;

    storage.get(2, [](comp_a& a, comp_c& c) {              // Doesn't work
    // storage.get<comp_a, comp_c>(2, [](comp_a& a, comp_c& c) { // Works
        std::cout << a.val << " " << c.val << std::endl;
    });
}

Un guide de déduction serait-il viable dans cette situation? Les informations de type semblent un peu enfouies dans le type de la std :: function , et je ne sais donc pas comment je les extraire dans un guide.

A Un exemple en direct est disponible ici .


3 commentaires

Je n'ai pas de réponse à votre question spécifique, mais pourquoi utiliser la std :: function ? Ne pourriez-vous pas vous en tirer en utilisant identity :: type (ou même void (Ts & ...) ?


Malheureusement, un pointeur de fonction ordinaire ne fonctionnerait pas car AFAIK exclut toute capture à l'intérieur du lambda (quelque chose que j'aimerais faire, bien que je ne le fasse pas dans cet exemple). Il semble également échouer la déduction d'argument lorsque je l'essaye. L'exécuter à travers l'identité présente le même problème lorsque la déduction est aveugle aux paramètres de la fonction.


une utilisation d'un type d'identité est exactement ce que vous essayez d'éviter. Cela rend votre argument non déductible de sorte qu'il doit être spécifié dans une liste de types de modèles. Donc je ne sais pas pourquoi c'est là ...


3 Réponses :


1
votes

Je ne sais pas à quoi sert la structure identity mais sa suppression donne un message d'erreur plus clair (la déduction du modèle a échoué).

Le compilateur est incapable de dériver le std: : type de fonction du lambda. Pour le prouver, ce qui suit compile:

namespace detail
{
    template < typename T > struct deduce_type;

    template < typename RETURN_TYPE, typename CLASS_TYPE, typename... ARGS >
    struct deduce_type< RETURN_TYPE(CLASS_TYPE::*)(ARGS...) const >
    {
        using type = std::function< RETURN_TYPE(ARGS...) >;
    };
}

template<size_t size, typename ... Cs>
struct storage
{
    template<typename ... Ts>
    void get(size_t index, typename std::function<void(Ts& ...)> f)
    {
        f(std::get<Ts::id>(components)[index] ...);
    }

    template<typename Lambda>
    void get(size_t index, Lambda l)
    {
        get( index, typename detail::deduce_type< decltype( &Lambda::operator() ) >::type( l ) );
    }

    std::tuple<std::array<Cs, size> ...> components;
};

Donc, pour que cela fonctionne, nous devons simplement aider le compilateur à dériver les types. Emprunt à http://www.cplusplus.com/forum/general/223816/ les travaux suivants:

storage.get(2, std::function<void(comp_a& a, comp_c& c)>([](comp_a& a, comp_c& c) {              // Doesn't work
    std::cout << a.val << " " << c.val << std::endl;
}));


0 commentaires

1
votes

Vous avez balisé C ++ 17, vous pouvez donc utiliser les guides de déduction de std :: function

Ainsi, comme suggéré par Alan Birtles, vous pouvez recevoir le lambda comme un type simple , convertissez-le en std :: function (guides de déduction) et déduisez le type des arguments.

Quelque chose comme

template<size_t size, typename ... Cs>
struct storage
{    
    template<typename ... Ts>
    void get(size_t index, std::function<void(Ts& ...)> f)
    { f(std::get<Ts::id>(components)[index] ...); }

    template <typename F>
    void get(size_t index, F f)
    { get(index, std::function{f}); }

    std::tuple<std::array<Cs, size> ...> components;
};


3 commentaires

Même std :: remove_cv_t :: id .


@ Jarod42 - ehmmm ... pourquoi? Je veux dire: qu'est-ce qui peut aller mal sans cela?


Pour gérer std :: function .



2
votes

Vous pouvez utiliser l'astuce du guide de déduction std :: function pour extraire les arguments, mais vous ne voulez vraiment pas créer un std: : fonction . Vous voulez empiler dans lambda-land. std :: function ajoute une surcharge et une allocation en raison de l'effacement de type - mais il n'y a rien que vous faites qui nécessite réellement les avantages de l'effacement de type. C'est une perte et aucune victoire. Ne faites pas réellement une std::function .

Cela dit, vous avez bien sûr toujours besoin des arguments. Vous pouvez donc faire ceci:

template <typename T> using uncvref_t = std::remove_cv_t<std::remove_reference_t<T>>;

template <typename F, typename R, typename... Args>
void get_impl(size_t index, F f, type_t<std::function<R(Args...)>>) {
    f(std::get<uncvref_t<Args>::id>(components)[index] ...);
}

Fondamentalement, nous prenons certains appelables - puis nous en déduisons une std :: function . Cela nous donne un certain type. Dans l'exemple spécifique de OP, ce type est std :: function . Nous transmettons ensuite simplement ce type à une fonction différente - en tant qu'objet vide. Pas de frais généraux. Pour répéter, nous ne créons pas réellement une std :: function - nous transmettons simplement son type.

Cette autre fonction peut profiter de savoir ce que std :: function sait:

template <typename T> struct type { };

template <typename F>
void get(size_t index, F f) {
    using function_type = decltype(std::function(f));
    get_impl(index, f, type<function_type>{});
}

Vous avez besoin du uncvref_t pour gérer le cas où Args peut ou non être une référence ou un cv qualifié.

Maintenant, cela ne fonctionnera pas pour aucun appelable. Si vous passez un lambda générique, la déduction de std :: function échouera. Mais alors ... ça ne pouvait pas marcher de toute façon, donc ça ne semble pas être une grosse perte?


2 commentaires

Surpris et impressionné de voir à quel point cela optimise. Pourriez-vous m'expliquer quel rôle type < T> sert ici et pourquoi est-ce nécessaire?


@rtek Nous avons besoin du type de la std :: function à déduire pour obtenir les arguments, mais nous ne voulons pas créer un objet de ce type. type est juste un moyen de passer des types