Variadic templates et unpack

Une solution qui marche, mais n'y a t-il pas mieux ?

Le problème exposé dans ce sujet a été résolu.

Bonjour à tous.

Dans le cadre du défi Javaquarium, je me suis dis que j’allais me lancer dans un ECS en C++ moderne pour me faire la main. J’en suis à la partie où j’assigne des components à des entités. Le code suivant marche, mais je trouve le hack pas terrible.

class EntitiesComponentsManager
{
public:
    /**
     * @brief Assigne un composant à l'entité donnée.
     * 
     * @param entity L'entité à laquelle rattacher les composants.
     * @param component Le composant à rattacher.
     * @pre Le composant ne doit pas déjà être rattaché à l'entité.
     * @post Le composant est bien rattaché à l'entité.
     */
    void assignComponent(Entity entity, components::Component && component);

    /**
     * @brief Associe un ou plusieurs composants à une entité.
     * 
     * @tparam Components 
     * @param entity L'entité à laquelle rattacher les composants.
     * @param first Le premier composant à rattacher.
     * @param components Un nombre indéfini de composants.
     * @pre Tous les composants doivent dériver du type components::Component.
     */
    template <typename Component, class ...Args>
    void assignComponents(Entity entity, Component && first, Args&&... components)
    {
        static_assert((std::is_base_of_v<components::Component, Args>&& ...), "All components must be sub-types of components::Component.");
        assignComponent(entity, std::forward<components::Component>(first));

        // Hack pour permettre l'ajout des composants variadiques sans erreur.
        int _[] { 0, (assignComponent(entity, std::forward<components::Component>(components)), 0)... };
        // Cast pour empêcher le compilateur de se plaindre.
        (void)_;
    }

    // Du code...
};

Code que j’utilise ensuite comme ça.

Entity create_seeweed(EntitiesComponentsManager & manager) noexcept
{
    using namespace components;

    Entity seeweed { generate() };
    
    // Une algue vit.
    Health health {};
    health.life = 15;
    health.state = Health::State::Good;

    // Ajout de tous les composants pour une algue.
    manager.assignComponents(seeweed, health, Vegetal {});
    return seeweed;
}

N’y a t-il pas mieux que la ligne suivante pour arriver à mes fins ? Ça marche bien, ça fait ce que je veux, mais ça n’a pas l’air très propre.

// Hack pour permettre l'ajout des composants variadiques sans erreur.
int _[] { 0, (assignComponent(entity, std::forward<components::Component>(components)), 0)... };

Merci d’avance à tous pour votre aide.

@informaticienzero

EDIT : j’ai trouvé cette solution aussi, mais je ne sais pas ce qu’elle vaut. Est-elle mieux ?

template <typename Component, class ...Args>
void assignComponents(Entity entity, Component && first, Args&&... components)
{
    assignComponent(entity, std::forward<components::Component>(first));
    (assignComponent(entity, std::forward<components::Component>(components)), ...);
}

La solution avec int[] est ce qui se fait avant C++17, parce qu’il n’y a pas vraiment d’autre solution. Personnellement, je préfère avoir une macro¹ pour cacher les contraintes et la lourdeur d’écriture (interdiction d’un tableau de taille 0, empêcher la surcharge de ,, faire disparaître les avertissements de variable non-utilisée et/ou de cast).

Les fold expressions de C++17 sont fait pour, mais il y a toujours la surcharge de , qui peut être embêtante, même si finalement assez rare. Dans ton cas de figure, je préfère la version avec les folds.

¹ Ma macro:

#if JLN_CXX_STD_VERSION >= 17
#  define JLN_UNPACK(...) (void((__VA_ARGS__)), ...)
#else
  namespace jln { using swallow = int[]; }
#  define JLN_UNPACK(...) void(::jln::swallow{((void)((__VA_ARGS__)), 0)...})
#endif
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte