Comment les types non spécifiés peuvent-ils être utilisés dans les expressions «requiert» C ++ 20?

Aug 16 2020

J'essaie d'écrire un concept C ++ 20 pour exprimer l'exigence qu'un type ait une certaine méthode, qui prend un argument, mais aux fins de ce concept, je me fiche du type d'argument.

J'ai essayé d'écrire quelque chose comme:

template <typename T>
concept HasFooMethod = requires(T t, auto x)
{
    { t.Foo(x) } -> std::same_as<void>;
};

cependant, gcc et clang le rejettent, donnant une erreur indiquant que «auto» ne peut pas être utilisé dans la liste de paramètres d'une expression requiert de cette façon.

Une alternative serait de mettre le type de 'x' comme deuxième paramètre de modèle:

template <typename T, typename TX>
concept HasFooMethod = requires(T t, TX x)
{
    { t.Foo(x) } -> std::same_as<void>;
};

mais alors cela nécessite que TX soit spécifié explicitement chaque fois que le concept est utilisé, il ne peut pas être déduit:

struct S { void Foo(int); };
static_assert(HasFooMethod<S>);         // doesn't compile
static_assert(HasFooMethod<S, int>);    // the 'int' must be specified

Existe-t-il un moyen d'écrire un concept qui permet à Foo de prendre un argument de type non spécifié ?

La question Définition de concept nécessitant une fonction membre de modèle contrainte est très similaire, mais pas la même: cette question demande comment exiger qu'une méthode (basée sur un modèle) puisse prendre n'importe quel type satisfaisant un concept donné, alors que cette question consiste à exiger qu'une méthode prenne un type particulier, bien que ce type ne soit pas spécifié. En termes de quantificateurs, l'autre question concerne la quantification universelle (bornée) tandis que celle-ci concerne la quantification existentielle. La réponse à l'autre question ne s'applique pas non plus à mon cas.

Réponses

4 NicolBolas Aug 17 2020 at 13:48

Les concepts ne sont pas destinés à fournir le type de fonctionnalité que vous recherchez. Donc, ils ne le fournissent pas.

Un concept est destiné à contraindre les modèles, à spécifier un ensemble d'expressions ou d'instructions qu'un modèle a l'intention d'utiliser (ou du moins être libre d'utiliser) dans sa définition.

Dans le modèle que vous êtes si contraignant, si vous écrivez l'expression t.Foo(x), vous connaissez le type de x. Il s'agit d'un type concret, d'un paramètre de modèle ou d'un nom dérivé d'un paramètre de modèle. Dans tous les cas, le type de xest disponible au niveau du modèle contraint.

Donc, si vous souhaitez contraindre un tel modèle, vous utilisez à la fois le type tet le type de x. Les deux sont à votre disposition à ce moment-là, il n'y a donc aucun problème à créer une telle contrainte. Autrement dit, la contrainte n'est pas activée en Ttant que type isolé; c'est sur l'association entre Tet X.

Les concepts ne sont pas destinés à fonctionner dans le vide, sans aucune association avec le lieu réel d'utilisation de la contrainte. Vous ne devriez pas vous concentrer sur la création de concepts unaires afin que les utilisateurs puissent static_assertleurs classes contre eux. Les concepts ne sont pas destinés à tester si un type les remplit (ce qui est essentiellement ce que vous static_assertfaites); ils sont destinés à contraindre la définition de modèle qui les utilise .

Votre contrainte doit être FooCallableWith, non HasFooMethod.

NathanReed Aug 17 2020 at 16:30

Quelque chose de proche de cela peut être accompli en définissant un type d'adaptateur qui peut implicitement se convertir en (presque) n'importe quoi:

struct anything
{
    // having both these conversions allows Foo's argument to be either
    // a value, an lvalue reference, or an rvalue reference

    template <typename T>
    operator T&();

    template <typename T>
    operator T&&();
};

Notez que ces opérateurs n'ont pas besoin d'être implémentés, car ils ne seront utilisés que dans un contexte non évalué (et en effet, ils ne pourraient pas être implémentés pour tous les types T).

Ensuite, HasFooMethodpeut s'écrire:

template <typename T>
concept HasFooMethod = requires(T t, anything a)
{
    { t.Foo(a) } -> std::same_as<void>;
};