web-dev-qa-db-fra.com

Plusieurs arguments typename dans un modèle c ++?

Comment puis-je avoir plusieurs arguments typename dans un modèle c ++?

#ifndef _CALL_TEMP_H
#define _CALL_TEMP_H

#include <string>
#include <iostream>

template <typename Sig>
class Foo;

template <typename A, typename B>
class Foo
{
    public:
        void output() {
            std::cout << a_ << b_ << std::endl;
        }
        A a_;
        B b_;
};

template <typename A, typename B, typename C>
class Foo
{
    public:
        void output() {
            std::cout << a_ << b_ << c_ << std::endl;
        }
        A a_;
        B b_;
        C c_;
};

#endif

Usage:

int main()
{
    Foo<int ,int> doubleint;
    doubleint.a_ = 1;
    doubleint.b_ = 2;
    doubleint.output();
//  Foo<int , int , std::string> comp;
//  comp.a_ = 1;
//  comp.b_ = 2;
//  comp.c_ = "haha";
//  comp.output();
    return 0;
}

Mais ça ne compilera pas. Comment pourrais-je le faire compiler?

12
Jichao

Il suffit de déclarer un modèle principal avec un modèle variadique, puis de se spécialiser pour chaque nombre d'arguments pris en charge. Par exemple:

#ifndef CALL_TEMP_H
#define CALL_TEMP_H

#include <iostream>

template <typename...> class Foo;

template <typename A, typename B>
class Foo<A, B>
{
public:
    void output() {
        std::cout << a_ << b_ << '\n';
    }
    A a_;
    B b_;
};

template <typename A, typename B, typename C>
class Foo<A, B, C>
{
public:
    void output() {
        std::cout << a_ << b_ << c_ << '\n';
    }
    A a_;
    B b_;
    C c_;
};

#endif

Si vous ne pouvez pas utiliser C++ 11 et que vous souhaitez conserver une notation similaire, vous devrez simuler une liste d’arguments variadique avec des arguments de modèle par défaut. Cela limitera implicitement le nombre d'arguments templare, mais puisque vous spécialisez les modèles de toute façon, cette limitation n'a pas d'importance.

S'il est acceptable d'utiliser une notation différente, vous pouvez également utiliser quelque chose qui ressemble à une déclaration de fonction pour instancier et spécialiser votre modèle:

template <typename> class Foo;

template <typename A, typename B>
class Foo<void(A, B)> {
    ...
};
template <typename A, typename B, typename C>
class Foo<void(A, B, C)> {
    ...
};
...
Foo<void(int, int)>                   f2;
Foo<void(int, int, std::string)> f3;

Le fait que le changement de notation soit acceptable ou non dépend de votre utilisation du modèle de classe. Cependant, vous ne réaliserez pas une solution idéale comme avec les modèles variadiques sans C++ 11.

BTW, ne pas abuser de std::endl : utilisez '\n' pour signifier la fin de la ligne. Si vous voulez vraiment vider le flux, utilisez std::flush. De plus, _CALL_TEMP_H est un nom réservé à la bibliothèque C++ standard, comme tous les noms commençant par un trait de soulignement suivi d'un caractère majuscule: ne pas utilisez ces noms dans votre propre code, sauf autorisation explicite de les utiliser (par exemple: __FILE__ et __LINE__ sont réservés mais la permission explicite de les utiliser est accordée).

30
Dietmar Kühl

Si vous avez plusieurs versions d'un modèle, vous devez en spécialiser une seule version. Si vous voulez un nombre différent d'arguments, l'astuce consiste à utiliser une classe de balises pour dire "cet argument n'est pas un argument", et de le définir comme argument par défaut. 

Dans votre cas, quelque chose comme les œuvres suivantes (compilées et testées):

#include <iostream>

// tag class indicating "no member in this place"
struct nothing {};

template <typename A, typename B, typename C = nothing> // <- note default arg.
class Foo;

template <typename A, typename B>
class Foo<A, B, nothing> // <- note specialization
{
    public :
        void output() {
            std::cout << a_ << b_ << std::endl;
        }

        A a_;
        B b_;
};

template <typename A, typename B, typename C>
class Foo
{
    public :
        void output() {
            std::cout << a_ << b_ << c_ << std::endl;
        }

        A a_;
        B b_;
        C c_;
};

int main()
{
    Foo<int, int> doubleint;
    doubleint.a_ = 1;
    doubleint.b_ = 2;
    doubleint.output();

    Foo<int, int, int> tripleint;
    tripleint.a_ = 1;
    tripleint.b_ = 2;
    tripleint.c_ = 3;
    tripleint.output();
}

Notez que ceci est essentiellement une réinvention de boost :: Tuple <>/std :: Tuple <>, sur lequel vous devriez absolument lire.

1
Kaz Dragon

Je pense que vous confondez la spécialisation avec la surcharge du même nom de classe. Vous ne pouvez pas créer une classe portant le même nom avec plusieurs arguments de modèle.

0
legends2k

Il ne compilera pas car vous ne pouvez pas définir la même classe plusieurs fois avec un nombre différent d'arguments de modèle.

Si vous connaissez le nombre maximal de paramètres de modèle que vous souhaitez prendre en charge, vous pouvez utiliser une spécialisation partielle:

// main template
template <typename A, typename B = void, typename C = void>
struct Foo
{
    void output() { std::cout << a_ << b_ << c_ << std::endl; }
    A a_;
    B b_;
    C c_;
};

// Partial specialisation for two parameters
template <typename A, typename B>
struct Foo<A, B, void>
{
    void output() { std::cout << a_ << b_ << c_ << std::endl; }
    A a_;
    B B_;
};

// Partial specialisation for one parameter
template <typename A>
struct Foo<A, void, void>
{
    void output() { std::cout << a_ << std::endl; }
    A a_;
};

Si vous utilisez C++ 11, une autre option serait d'utiliser des modèles variadiques.

0
Tristan Brindle