web-dev-qa-db-fra.com

Intégrer le nom du type dans la sortie static_assert?

J'aime donner des erreurs/messages utiles, et je veux aussi le faire pour mon static_asserts. Le problème est qu'ils dépendent des paramètres du modèle. Normalement, ces paramètres seront affichés sur un chemin ou un autre en raison de l'erreur générée, mais ils sont soit obscurs, soit non regroupés, ce qui leur donne un sens. Exemple:

template<class T>
struct fake_dependency{
  static bool const value = false;
};

template<class T, class Tag>
struct Foo{
  Foo(){}

  template<class OtherTag>
  Foo(Foo<T, OtherTag> const&){
    static_assert(fake_dependency<T>::value, "Cannot create Foo<T,Tag> from Foo<T,OtherTag>.");
  }
};

int main(){
    Foo<int, struct TagA> fA;
    Foo<int, struct TagB> fB(fA);
}

Sortie sur MSVC:

src\main.cpp(74): error C2338: Cannot create Foo<T,Tag> from Foo<T,OtherTag>.
          src\main.cpp(84) : see reference to function template instantiation 'Foo<T,Tag>::Foo<main::TagA>(const Foo<T,main::TagA> &)' being compiled
          with
          [
              T=int,
              Tag=main::TagB
          ]

Une balise est mentionnée dans le modèle de fonction lui-même, l'autre en dessous avec le modèle de classe. Pas si cool. Voyons ce que les sorties GCC :

prog.cpp: In constructor 'Foo<T, Tag>::Foo(const Foo<T, OtherTag>&) [with OtherTag = main()::TagA, T = int, Tag = main()::TagB]':
prog.cpp:18:32:   instantiated from here
prog.cpp:12:5: error: static assertion failed: "Cannot create Foo<T,Tag> from Foo<T,OtherTag>."

Bien mieux, mais pas vraiment là où se trouve le static_assert. Et maintenant, imaginez quelques paramètres supplémentaires, plusieurs modèles, ou les deux. frissons

Une façon de contourner ce problème consiste à utiliser une structure intermédiaire, qui prend les deux balises comme paramètres de modèle:

template<class Tag, class OtherTag>
struct static_Foo_assert{
    static_assert(fake_dependency<Tag>::value, "Cannot create Foo<T,Tag> from Foo<T,OtherTag>.");
};

template<class T, class Tag>
struct Foo{
  Foo(){}

  template<class OtherTag>
  Foo(Foo<T, OtherTag> const&){
      static_Foo_assert<Tag, OtherTag> x;
  }
};

Voyons maintenant la sortie:

src\main.cpp(70): error C2338: Cannot create Foo<T,Tag> from Foo<T,OtherTag>.
          src\main.cpp(79) : see reference to class template instantiation 'static_Foo_assert<Tag,OtherTag>' being compiled
          with
          [
              Tag=main::TagB,
              OtherTag=main::TagA
          ]

Beaucoup mieux! Voici ce que GCC dit :

prog.cpp: In instantiation of 'static_Foo_assert<main()::TagB, main()::TagA>':
prog.cpp:17:40:   instantiated from 'Foo<T, Tag>::Foo(const Foo<T, OtherTag>&) [with OtherTag = main()::TagA, T = int, Tag = main()::TagB]'
prog.cpp:23:32:   instantiated from here
prog.cpp:8:5: error: static assertion failed: "Cannot create Foo<T,Tag> from Foo<T,OtherTag>."

Ça n'a pas l'air mauvais. Le problème: je dois créer une telle structure pour chaque modèle, car le message d'erreur dans static_assert doit être un littéral de chaîne ...

Maintenant, pour ma question: Pouvons-nous en quelque sorte inclure les noms de type directement dans le static_assert? Comme

static_assert(..., "Cannot create Foo<" T "," Tag "> from Foo<" T "," OtherTag ">.");

Exemple de sortie:

Impossible de créer Foo<int,main::TagA> à partir de Foo<int,main::TagB>.

Ou, si cela n’est pas réalisable, pouvons-nous en quelque sorte faire du message d’erreur un paramètre de modèle supplémentaire, afin de le rendre passable?

50
Xeo

Mon bidouille

Code:

template <typename Assertion>
struct AssertValue : AssertionChecker<Assertion::value, Assertion>
{
    static_assert(AssertionValue, "Assertion failed <see below for more information>");
    static bool const value = Assertion::value;
};

Il vous permet de vérifier toute assertion ::value et de vider les types en cas d'échec.

Usage:

// Bad indentation used to show parts
static_assert(
    AssertValue<
        std::my_check<
            T0, decltype(*somethingComplicated), T7::value_type
        >
    >, 
    "something horrible happened"
);

std::my_check<...>::value est le résultat booléen de la vérification 

Exemple

Pour un exemple SSCCE complet, voir: Exemple IDEOne

Le message d'erreur de l'exemple:

prog.cpp: In instantiation of 'AssertValue<std::is_base_of<IMyInterface, MyBadType> >':
prog.cpp:37:69:   instantiated from 'void MyFunction(IteratorType, IteratorType) [with IteratorType = __gnu_cxx::__normal_iterator<MyBadType*, std::vector<MyBadType> >]'
prog.cpp:60:38:   instantiated from here
prog.cpp:9:5: error: static assertion failed: "Assertion failed <see below for more information>"
prog.cpp: In function 'void MyFunction(IteratorType, IteratorType) [with IteratorType = __gnu_cxx::__normal_iterator<MyBadType*, std::vector<MyBadType> >]':
prog.cpp:60:38:   instantiated from here
prog.cpp:39:5: error: static assertion failed: "iterator passed does not reference IMyInterface items"

Explication

Si l'assertion échoue, les arguments de modèle de AssertValue seront imprimés et par conséquent, l'extension complète du modèle de votre chèque. Par exemple, si vous vérifiez un std::is_base_of, le type complet du chèque sera imprimé, par exemple: std::is_base_of<IMyInterface, MyBadType>. Ensuite, vous savez exactement quels types ont été utilisés dans l'assertion ayant échoué.

Le seul problème est que cela ne fonctionne que sur les modèles qui mettent leur résultat dans ::value. Cependant, type_traits utilise principalement ceci et est la norme goto.

11
Bob Fincheimer

Il est possible de faire passer un littéral de chaîne en tant que paramètre de type non-type, avec un peu de hoop-jumping . Mais comme le deuxième argument de static_assert est contraint d’être un littéral de chaîne plutôt que, par exemple, une expression constante d’adresse, cela n’est malheureusement pas très utile.

Malheureusement, je soupçonne que votre meilleur choix est de faire pression sur le comité ou les rédacteurs du compilateur afin d’étendre les installations.

3
Alan Stokes

Si votre compilateur fournit la macro __FUNCTION__, vous pouvez effectuer une substitution très simple à l'aide de la concaténation littérale. Cependant, l'implémentation de gcc et clang n'est pas faite en macro, cette solution ne fonctionnera donc pas pour eux.

#include "stdafx.h"
#include <type_traits>

template <class T>
class must_be_pod
{
    static void test() { static_assert (std::is_pod<T>::value, __FUNCTION__ ": not a POD"); }
public:
    must_be_pod() { test(); }
};

class not_a_pod
{
public:
    not_a_pod() {}
    virtual ~not_a_pod() {}
};

int main()
{
    must_be_pod<not_a_pod> should_fail; // and it does
    return 0;
}

Cela produit la sortie suivante lors de la compilation par VS2015:

static_assert_test.cpp(10): error C2338: must_be_pod<class not_a_pod>::test: not a POD
2
Spencer

Je vois que cela a été répondu il y a quelque temps, mais la réponse complète a été perdue et j'ai trouvé un moyen très simple d'obtenir le résultat souhaité.

template <typename T, bool value>
static typename std::enable_if<value, void>::type FunctionWithReadableErrorMessage()
{
}


int  main()
{
    FunctionWithReadableErrorMessage<int, false>();
    return 0;
}

Cette fonction compilera et n'aura aucun effet si value = true, sinon nous obtenons ce message d'erreur:

main.cpp: Dans la fonction 'int main ()': main.cpp: 16: 50: erreur: pas de fonction correspondante pour l'appel à 'FunctionWithReadableErrorMessage ()' FunctionWithReadableErrorMessage (); ^

Si nous voulons être un peu plus généraux, nous pouvons le mettre dans une macro 

0
Daniel Duvilanski