8
votes

Un moyen sûr et conforme aux normes de faire échouer la compilation d'une spécialisation de modèle de classe en utilisant `static_assert` uniquement si elle est instanciée?

Supposons que nous voulions créer une classe modèle qui ne peut être instanciée qu'avec des nombres et ne devrait pas se compiler autrement. Ma tentative:

#include <type_traits>

template<typename T, typename = void>
struct OnlyNumbers{
public:
    struct C{};
    static_assert(std::is_same<C,T>::value, "T is not arithmetic type.");

    //OnlyNumbers<C>* ptr;
};

template<typename T>
struct OnlyNumbers<T, std::enable_if_t<std::is_arithmetic_v<T>>>{};

struct Foo{};
int main()
{
    OnlyNumbers<int>{}; //Compiles
    //OnlyNumbers<Foo>{}; //Error
}

Démo en direct - Les trois compilateurs principaux semblent pour fonctionner comme prévu. Je sais qu'il existe déjà un question avec des réponses citant la norme. La réponse acceptée utilise temp.res.8 avec temp.dep.1 pour répondre à cette question. Je pense que ma question est un peu différente parce que je pose précisément sur mon exemple et je ne suis pas sûr de l'opinion du standard à ce sujet.

Je dirais que mon programme n'est pas mal formé et que il devrait échouer à compiler si et seulement si le compilateur tente d'instancier le modèle de base. Mon raisonnement:

  • [temp.dep.1]:

    À l'intérieur d'un modèle, certaines constructions ont une sémantique qui peut différer d'une instanciation à une autre. Une telle construction dépend des paramètres du modèle.

    Cela devrait rendre std :: is_same :: value dépendant de T.

  • [temp.res.8.1]:

    aucune spécialisation valide ne peut être générée pour un modèle ou une sous-déclaration d'une constexpr si l'instruction dans un modèle et le modèle n'est pas instancié, ou

    Ne s'applique pas car il existe une spécialisation valide, en particulier OnlyNumbers est valide et peut être utilisé à l'intérieur de la classe pour par exemple définir une variable de pointeur membre ( ptr ). En effet en supprimant l'assert et en décommentant les lignes ptr , OnlyNumbers le code se compile.

  • [temp.res.8.2 - 8.4] ne s'applique pas.

  • [temp.res.8.5] Je ne pense pas que cela s'applique non plus, mais je ne peux pas dire que je comprends parfaitement cette section.

Ma question est: Mon raisonnement est-il correct? Est-ce un moyen sûr et conforme aux normes de faire échouer la compilation d'un modèle [class] * particulier en utilisant static_assert si ** et seulement s'il est instancié?

* Principalement I Je suis intéressé par les modèles de classes, n'hésitez pas à inclure des modèles de fonctions. Mais je pense que les règles sont les mêmes.

** Cela signifie qu'il n'y a pas de T qui peut être utilisé pour instancier le modèle de l'extérieur comme T = C pourrait être utilisé de l'intérieur. Même si C pouvait être consulté d'une manière ou d'une autre, je ne pense pas qu'il y ait moyen de s'y référer car cela conduit à cette récursivité OnlyNumbers :: C> code >.

EDIT:

Juste pour clarifier, je sais que je peux construire une expression qui sera exactement fausse si aucune des autres spécialisations ne correspond. Mais cela peut devenir verbeux assez rapidement et est sujet aux erreurs si les spécialisations changent.


0 commentaires

3 Réponses :


1
votes

Eh bien ... je ne comprends pas ce que tu veux dire par

[[temp.res.8.1]] Ne s'applique pas car il existe une spécialisation valide, en particulier OnlyNumbers est valide et peut être utilisé à l'intérieur de la classe par exemple. définir une variable de pointeur de membre (ptr).

Pouvez-vous donner un exemple de modèle principal OnlyNumers valide et de compilation basé sur OnlyNumbers?

Quoi qu'il en soit, il Il me semble que le point est exactement ceci.

Si vous demandez

Est-ce un moyen sûr et conforme aux normes de faire échouer la compilation d'un modèle [class] * particulier à l'aide de static_assert si ** et seulement s'il est instancié?

il me semble que (peut-être en excluant un test qui n'est vrai que lorsqu'une autre spécialisation correspond) la réponse est "non" à cause de [temp.res.8.1].

Peut-être vous pouvez laisser une petite porte ouverte pour permettre une instanciation, mais seulement disponible si quelqu'un veut vraiment (vraiment!) l'instancier.

Par exemple, vous pouvez ajouter un troisième paramètre de modèle, avec une valeur par défaut différente, et quelque chose comme suit

// declared but (main version) not defined
template<typename T, typename = void>
struct OnlyNumbers;

// only specialization defined
template<typename T>
struct OnlyNumbers<T, std::enable_if_t<std::is_arithmetic_v<T>>>
 { };

De cette façon, vous ouvrez une porte à une instanciation légitime

OnlyNumbers<Foo, Foo, Foo>     o1;
OnlyNumbers<void, void, void>  o2;
OnlyNumbers<int, int>          o3;

mais n'expliquant qu'au moins une seconde type de template.

Quoi qu'il en soit, pourquoi ne pas simplement éviter de définir la version principale du template?

template<typename T, typename U = void, typename V = int>
struct OnlyNumbers
 {
   static_assert(std::is_same<T, U>::value, "test 1");
   static_assert(std::is_same<T, V>::value, "test 2");
 };


1 commentaires

Merci pour votre réponse. Apparemment, j'avais tort, je pensais que OnlyNumbers à l'intérieur de la classe créerait un modèle valide, mais ce n'est pas le cas. Parce qu'il va créer un autre C qui est différent de celui passé en argument. Laisser le modèle principal non déclaré fonctionne, mais je n'aime tout simplement pas le message d'erreur. Avoir static_assert avec custom est plus lisible. Ajouter un argument supplémentaire était en effet mon idée originale, mais ce devrait être quelque chose qui n'est pas valide T et ma solution était d'essayer de rendre ce T invalide sur place, d'où le C inaccessible. Je m'en tiendrai donc uniquement à la déclaration.



3
votes

Les assertions statiques sont là pour être utilisées directement dans la classe sans rien faire de compliqué.

#include <type_traits>

template<typename T>
struct OnlyNumbers {
    static_assert(std::is_arithmetic_v<T>, "T is not arithmetic type.");
    using TT = std::conditional_t<std::is_arithmetic_v<T>,T,int>;
    // ....
};

Dans certains cas, vous pouvez recevoir des messages d'erreur supplémentaires car l'instanciation de OnlyNumbers pour les types non arithmétiques peut entraîner plus d'erreurs de compilation.

Une astuce que j'ai utilisée de temps en temps est

#include <type_traits>

template<typename T>
struct OnlyNumbers {
    static_assert(std::is_arithmetic_v<T>, "T is not arithmetic type.");
    // ....
};

Dans ce cas, votre classe est instanciée avec int, un type valide . Puisque l'assertion statique échoue de toute façon, cela n'a pas d'effets négatifs.


0 commentaires

1
votes

Votre code est mal formé car le modèle principal ne peut pas être instancié. Voir la citation standard dans la réponse de Barry à la question connexe à laquelle vous avez lié. La manière détournée que vous avez utilisée pour vous assurer que l'exigence standard clairement énoncée ne peut être satisfaite n'aide pas. Arrêtez de combattre votre compilateur rsp. la norme, et suivez l'approche de Handy999. Si vous ne voulez toujours pas faire cela, par exemple pour des raisons sèches, une manière conforme d'atteindre votre objectif serait:

template<typename T, typename Dummy = void>
struct OnlyNumbers{
public:
    struct C{};
    static_assert(! std::is_same<Dummy, void>::value, "T is not a number type.");

Deux remarques:

  • Tout d'abord, j'ai volontairement remplacé le message d'erreur car le message d'erreur "n'est pas un type arithmétique" hurle que vous devez tester ! std :: is_arithmetic :: value . L'approche que j'ai décrite est potentiellement logique si vous avez plusieurs surcharges pour les types "numériques", dont certains répondent aux exigences de type arithmétique de la norme et d'autres non (par exemple, peut-être un type d'une bibliothèque multiprécision).
  • Deuxièmement, vous pourriez objecter que quelqu'un pourrait écrire par exemple OnlyNumbers pour contourner l'assertion statique. Ce à quoi je dis, c'est leur problème. N'oubliez pas que chaque fois que vous faites quelque chose d'idiot à l'épreuve, la nature fait un meilleur idiot. ;-) Sérieusement, faites des API faciles à utiliser et difficiles à abuser, mais vous ne pouvez pas réparer la folie et ne devriez pas vous donner la peine d'essayer.

TL; DR: KISS et SWYM (dites ce que vous voulez dire)


0 commentaires