86 votes

Existe-t-il un static_warning ?

Je suis au courant de cette question qui mentionne l'"AVERTISSEMENT STATIQUE" de Boost, mais j'aimerais demander à nouveau, plus précisément, comment je pourrais mettre en place une static_warning qui fonctionne de manière similaire à static_assert mais n'émet qu'un avertissement au moment de la compilation plutôt qu'une erreur de compilation avortée.

J'aimerais quelque chose de similaire à la proposition d'Alexandrescu pour un assert statique dans les jours pré-C++11 qui réussissait d'une manière ou d'une autre à imprimer des informations contextuelles utiles dans le cadre de l'erreur.

Il serait acceptable d'exiger que l'utilisateur active certains avertissements standard du compilateur pour que cette construction fonctionne (peut-être "conversion de pointeur invalide" ou "casse les règles strictes d'aliasing") -- tout avertissement qui devrait faire partie d'une compilation normale peut être utilisé.

En bref, je veux static_warning(false, "Hello world"); pour créer un avertissement du compilateur qui devrait d'une manière ou d'une autre inclure la chaîne "hello world" dans le message d'avertissement. Est-ce possible, disons dans GCC et MSVC, et comment ?

Je serais heureux de distribuer une petite prime pour toute solution particulièrement intelligente.


Un peu d'explication : J'ai eu l'idée en pensant à cette question : Un avertissement statique serait un moyen utile de tracer à travers le processus de compilation des spécialisations de modèles complexes, qui sont autrement assez difficiles à déboguer. Un avertissement statique pourrait être utilisé comme une simple balise pour que le compilateur émette "Je suis en train de compiler cette partie du code".


Mise à jour. Idéalement, l'avertissement serait déclenché dans la configuration suivante :

template <typename T> struct Foo
{
    static_warning(std::is_pointer<T>::value, "Attempting to use pointer type.");
    // ...
};

int main() { Foo<int> a; Foo<int*> b; }

0 votes

Recherchez-vous la portabilité ? Je sais que certains compilateurs implémentent des crochets similaires pour le préprocesseur ( #error , #warning , #message ), il serait peut-être judicieux de les implémenter dans gcc et Clang ?

2 votes

@VioletGiraffe : #warning concerne les avertissements du préprocesseur, pour autant que je sache, et n'a rien à voir avec les instanciations de modèles.

2 votes

GCC permet au deprecated à appliquer aux variables, aux types et aux fonctions ; il peut s'agir d'un message arbitraire (cf. gcc.gnu.org/onlinedocs/gcc/Type-Attributes.html#Type-Attributes ). J'ai essayé d'élaborer une solution en l'utilisant, mais jusqu'à présent, les détails m'échappent ; il pourrait s'agir d'un composant de solution viable, cependant.

56voto

Managu Points 5694

Pour faire suite au commentaire de Michael E :

#if defined(__GNUC__)
#define DEPRECATE(foo, msg) foo __attribute__((deprecated(msg)))
#elif defined(_MSC_VER)
#define DEPRECATE(foo, msg) __declspec(deprecated(msg)) foo
#else
#error This compiler is not supported
#endif

#define PP_CAT(x,y) PP_CAT1(x,y)
#define PP_CAT1(x,y) x##y

namespace detail
{
    struct true_type {};
    struct false_type {};
    template <int test> struct converter : public true_type {};
    template <> struct converter<0> : public false_type {};
}

#define STATIC_WARNING(cond, msg) \
struct PP_CAT(static_warning,__LINE__) { \
  DEPRECATE(void _(::detail::false_type const& ),msg) {}; \
  void _(::detail::true_type const& ) {}; \
  PP_CAT(static_warning,__LINE__)() {_(::detail::converter<(cond)>());} \
}

// Note: using STATIC_WARNING_TEMPLATE changes the meaning of a program in a small way.
// It introduces a member/variable declaration.  This means at least one byte of space
// in each structure/class instantiation.  STATIC_WARNING should be preferred in any 
// non-template situation.
//  'token' must be a program-wide unique identifier.
#define STATIC_WARNING_TEMPLATE(token, cond, msg) \
    STATIC_WARNING(cond, msg) PP_CAT(PP_CAT(_localvar_, token),__LINE__)

La macro peut être invoquée au niveau de l'espace de nom, de la structure et de la fonction. Étant donné l'entrée :

#line 1
STATIC_WARNING(1==2, "Failed with 1 and 2");
STATIC_WARNING(1<2, "Succeeded with 1 and 2");

struct Foo
{
  STATIC_WARNING(2==3, "2 and 3: oops");
  STATIC_WARNING(2<3, "2 and 3 worked");
};

void func()
{
  STATIC_WARNING(3==4, "Not so good on 3 and 4");
  STATIC_WARNING(3<4, "3 and 4, check");
}

template <typename T> struct wrap
{
  typedef T type;
  STATIC_WARNING(4==5, "Bad with 4 and 5");
  STATIC_WARNING(4<5, "Good on 4 and 5");
  STATIC_WARNING_TEMPLATE(WRAP_WARNING1, 4==5, "A template warning");
};

template struct wrap<int>;

GCC 4.6 (au niveau d'avertissement par défaut) produit :

static\_warning.cpp: In constructor ‘static\_warning1::static\_warning1()’:
static\_warning.cpp:1:1: warning: ‘void static\_warning1::\_(const detail::false\_type&)’ 
    is deprecated (declared at static\_warning.cpp:1): **_Failed with 1 and 2_** \[-Wdeprecated-declarations\]
static\_warning.cpp: In constructor ‘Foo::static\_warning6::static\_warning6()’:
static\_warning.cpp:6:3: warning: ‘void Foo::static\_warning6::\_(const detail::false\_type&)’
    is deprecated (declared at static\_warning.cpp:6): **_2 and 3: oops_** \[-Wdeprecated-declarations\]
static\_warning.cpp: In constructor ‘func()::static\_warning12::static\_warning12()’:
static\_warning.cpp:12:3: warning: ‘void func()::static\_warning12::\_(const detail::false\_type&)’ 
    is deprecated (declared at static\_warning.cpp:12): **_Not so good on 3 and 4_** \[-Wdeprecated-declarations\]
static\_warning.cpp: In constructor ‘wrap<T>::static\_warning19::static\_warning19() \[with T = int\]’:
static\_warning.cpp:24:17:   instantiated from here
static\_warning.cpp:19:3: warning: ‘void wrap<T>::static\_warning19::\_(const detail::false\_type&) \[with T = int\]’ 
    is deprecated (declared at static\_warning.cpp:19): **_Bad with 4 and 5_** \[-Wdeprecated-declarations\]

Alors que Visual C++ 2010 (à /W3 ou plus) dit :

warnproj.cpp(1): warning C4996: 'static\_warning1::\_': Failed with 1 and 2
warnproj.cpp(1) : see declaration of 'static\_warning1::\_'
warnproj.cpp(6): warning C4996: 'Foo::static\_warning6::\_': 2 and 3: oops
warnproj.cpp(6) : see declaration of 'Foo::static\_warning6::\_'
warnproj.cpp(12): warning C4996: 'func::static\_warning12::\_': Not so good on 3 and 4
warnproj.cpp(12) : see declaration of 'func::static\_warning12::\_'
warnproj.cpp(19): warning C4996: 'wrap<T>::static\_warning19::\_': Bad with 4 and 5
    with
    \[
        T=int
    \]
warnproj.cpp(19) : see declaration of 'wrap<T>::static\_warning19::\_'
    with
    \[
        T=int
    \]
warnproj.cpp(19) : while compiling class template member function 'wrap<T>::static\_warning19::static\_warning19(void)'
    with
    \[
        T=int
    \]
warnproj.cpp(24) : see reference to class template instantiation 'wrap<T>::static\_warning19' being compiled
    with
    \[
        T=int
    \]

Clang++ 3.1 sur Linux produit une sortie sans doute plus belle (couleur non montrée) :

tst3.cpp:1:1: warning: '\_' is deprecated: Failed with 1 and 2
      \[-Wdeprecated-declarations\]
STATIC\_WARNING(1==2, "Failed with 1 and 2");
^
tst3.cpp:24:38: note: expanded from macro 'STATIC\_WARNING'
  PP\_CAT(static\_warning,\_\_LINE\_\_)() {\_(::detail::converter<(cond)>());} \\
                                     ^
tst3.cpp:6:3: warning: '\_' is deprecated: 2 and 3: oops
      \[-Wdeprecated-declarations\]
  STATIC\_WARNING(2==3, "2 and 3: oops");
  ^
tst3.cpp:24:38: note: expanded from macro 'STATIC\_WARNING'
  PP\_CAT(static\_warning,\_\_LINE\_\_)() {\_(::detail::converter<(cond)>());} \\
                                     ^
tst3.cpp:12:3: warning: '\_' is deprecated: Not so good on 3 and 4
      \[-Wdeprecated-declarations\]
  STATIC\_WARNING(3==4, "Not so good on 3 and 4");
  ^
tst3.cpp:24:38: note: expanded from macro 'STATIC\_WARNING'
  PP\_CAT(static\_warning,\_\_LINE\_\_)() {\_(::detail::converter<(cond)>());} \\
                                     ^
tst3.cpp:19:3: warning: '\_' is deprecated: Bad with 4 and 5
      \[-Wdeprecated-declarations\]
  STATIC\_WARNING(4==5, "Bad with 4 and 5");
  ^
tst3.cpp:24:38: note: expanded from macro 'STATIC\_WARNING'
  PP\_CAT(static\_warning,\_\_LINE\_\_)() {\_(::detail::converter<(cond)>());} \\
                                     ^
tst3.cpp:23:17: note: in instantiation of member function
      'wrap<int>::static\_warning19::static\_warning19' requested here
template struct wrap<int>
                ^
4 warnings generated.

0 votes

Pourquoi n'utilisez-vous pas l'attribut gcc ? warning ?

0 votes

@PlasmaHH : Du texinfo de GCC : warning ("message") : " Si cet attribut est utilisé sur une déclaration de fonction et qu'un appel à une telle fonction n'est pas éliminé par des ... optimisations, un avertissement qui comprendra ... ". message sera diagnostiquée." Je ne voulais pas que l'optimisation soit un problème. Après tout, les appels de fonction STATIC_WARNING(...) sont conçus pour être entièrement optimisés.

0 votes

Ah, bizarre, j'avais l'impression que warning et deprecated avaient les mêmes exigences et juste un message différent.

14voto

GManNickG Points 155079

Voici ce que j'ai trouvé de mieux jusqu'à présent. Il est basique et ne répond pas tout à fait à vos exigences, mais il suit plutôt la voie du BOOST_MPL_ASSERT_MSG en ce sens que votre message doit prendre la forme d'un identifiant valide. (Pour autant que je sache, le seul moyen d'obtenir l'impression d'une chaîne de caractères dans le message d'avertissement est que l'avertissement que vous avez utilisé se trouve être également un avertissement concernant les chaînes de caractères, et qu'il imprime son contenu).

Il faut que l'avertissement pour une variable non utilisée soit activé. En g++, c'est -Wunused-variable (activé par -Wall ), et dans MSVC c'est l'avertissement C4101 qui est activé au niveau d'avertissement 3.

Il n'est évidemment pas très testé et pourrait être amélioré de quelques façons (utilisation de __COUNTER__ au lieu de __LINE__ sur les compilateurs supportés, impression de messages plus jolie, utilisation de Boost pour simplifier, etc.), mais semble faire le travail. Voici le texte passe-partout :

namespace detail
{
    template <bool Condition>
    struct static_warning;

    template <>
    struct static_warning<true>
    {
        template <typename Message>
        static void warn() {}
    };

    template <>
    struct static_warning<false>
    {
        // If you're here because of a warning, please see where the
        // template was instantiated for the source of the warning.
        template <typename Message>
        static void warn() { Message STATIC_WARNING_FAILED; }
    };
}

#define STATIC_WARNING_DETAIL_EX(cond, msg, line)                   \
        struct static_warning ## line                               \
        {                                                           \
            class msg {};                                           \
                                                                    \
            static_warning ## line()                                \
            {                                                       \
                ::detail::static_warning<(cond)>::                  \
                    warn<void************ (msg::************)()>(); \
            }                                                       \
        }

#define STATIC_WARNING_DETAIL(cond, msg, line) \
        STATIC_WARNING_DETAIL_EX(cond, msg, line)

// Use these:
#define STATIC_WARNING_MSG(cond, msg) \
        STATIC_WARNING_DETAIL(cond, msg, __LINE__)

#define STATIC_WARNING(cond) \
        STATIC_WARNING_DETAIL(cond, STATIC_WARNING_FAILED, __LINE__)

Et un test :

STATIC_WARNING(sizeof(int) == 2);

int main()
{
    STATIC_WARNING_MSG(sizeof(char) != 1, JUST_KIDDING_ALL_IS_WELL);
}

En MSVC, cela donne :

>main.cpp(19): warning C4101: 'STATIC_WARNING_FAILED' : unreferenced local variable
>          main.cpp(45) : see reference to function template instantiation 'void detail::static_warning<false>::warn<void************(__thiscall static_warning45::STATIC_WARNING_FAILED::* ***********)(void)>(void)' being compiled
>main.cpp(19): warning C4101: 'STATIC_WARNING_FAILED' : unreferenced local variable
>          main.cpp(49) : see reference to function template instantiation 'void detail::static_warning<false>::warn<void************(__thiscall main::static_warning49::JUST_KIDDING_ALL_IS_WELL::* ***********)(void)>(void)' being compiled

Et dans GCC il produit :

main.cpp: In static member function 'static void detail::static_warning<false>::warn() [with Message = void************ (static_warning39::STATIC_WARNING_FAILED::************)()]':
main.cpp:39:1:   instantiated from here
main.cpp:19:38: warning: unused variable 'STATIC_WARNING_FAILED'
main.cpp: In static member function 'static void detail::static_warning<false>::warn() [with Message = void************ (main()::static_warning43::JUST_KIDDING_ALL_IS_WELL::************)()]':
main.cpp:43:5:   instantiated from here
main.cpp:19:38: warning: unused variable 'STATIC_WARNING_FAILED'

3 votes

C'est très intéressant, mais cela ne semble fonctionner que dans une fonction libre ou un contexte global. Je n'arrive pas à lui faire afficher des avertissements à l'intérieur d'un modèle de classe...

5voto

user763305 Points 6123

Voici une solution qui utilise la bibliothèque Boost MPL :

#include <boost/mpl/eval_if.hpp>
#include <boost/mpl/identity.hpp>
#include <boost/mpl/print.hpp>

#define static_warning_impl2(cond, msg, line) \
    struct static_warning_ ## line { \
        struct msg {}; \
        typedef typename boost::mpl::eval_if_c< \
            cond, \
            boost::mpl::identity<msg>, \
            boost::mpl::print<msg> \
        >::type msg ## _; \
    }

#define static_warning_impl1(cond, msg, line) \
    static_warning_impl2(cond, msg, line)

#define static_warning(cond, msg) \
    static_warning_impl1(cond, msg, __LINE__)

Elle s'accompagne de la même restriction que la solution de GMan : le message doit être un identifiant valide. Voici deux tests

static_warning(sizeof(int) == 4, size_of_int_is_not_4);

y

static_warning(sizeof(int) == 2, size_of_int_is_not_2);

Avec MSVS 2010 le premier test compile sans avertissement, le second compile avec l'avertissement

C:\Libraries\Boost\boost_1_48_0\boost/mpl/print.hpp(51): warning C4308: negative integral constant converted to unsigned type
    C:\Libraries\Boost\boost_1_48_0\boost/mpl/eval_if.hpp(63) : see reference to class template instantiation 'boost::mpl::print<T>' being compiled
    with
    [
        T=static_warning_28::size_of_int_is_not_2
    ]
    Test.cpp(28) : see reference to class template instantiation 'boost::mpl::eval_if_c<C,F1,F2>' being compiled
    with
    [
        C=false,
        F1=boost::mpl::identity<static_warning_28::size_of_int_is_not_2>,
        F2=boost::mpl::print<static_warning_28::size_of_int_is_not_2>
    ]

Le code utilise boost::mpl::print. Extrait du livre Métaprogrammation des modèles C++ par D. Abrahams et A. Gurtovoy, page 171 :

Pour générer un journal d'exécution au moment de la compilation, il nous faudrait un moyen de générer un message de diagnostic - un avertissement. Parce qu'il n'y a pas de construction unique qui fera en sorte que tous les compilateurs génèrent un avertissement (en fait, la plupart des compilateurs vous permettent de désactiver complètement les avertissements), MPL a une fonction print qui est juste comme identity sauf qu'il est réglé pour générer un avertissement sur une variété de compilateurs populaires avec leurs paramètres habituels.

0 votes

Je pense que vous manquez #include <boost/mpl/eval_if.hpp> et aussi typename devant le boost::eval_if_c . Quoi qu'il en soit, je n'arrive pas à faire imprimer quoi que ce soit (GCC 4.6.2) ; la compilation se déroule sans aucun message...

0 votes

J'ai corrigé le #include manquant <boost/mpl/eval_if.hpp> et le typename manquant. Si le code ne génère aucun avertissement avec GCC 4.6.2, alors il s'agit probablement d'un bug de boost::mpl::print. Quelle version de Boost utilisez-vous ?

0 votes

Ce qui se passe sous GCC 4.6.2 si vous compilez #include <boost/mpl/print.hpp> NEWLINE boost::mpl::print<int>::type a ;

3voto

Burak Points 1353

Desde C++14 vous pouvez utiliser l'attribut [[deprecated]] .

#include <iostream>

[[deprecated]]
void TriassicPeriod() {
    std::clog << "Triassic Period: [251.9 - 208.5] million years ago.\n";
}

[[deprecated("Use NeogenePeriod() instead.")]]
void JurassicPeriod() {
    std::clog << "Jurassic Period: [201.3 - 152.1] million years ago.\n";
}

[[deprecated("Use calcSomethingDifferently(int).")]]    
int calcSomething(int x) {
    return x * 2;
}

int main()
{
    TriassicPeriod();
    JurassicPeriod();
}

CCG (x86-64 gcc 10.3) --std=c++14 -Wall :

<source>: In function 'int main()':
<source>:20:20: warning: 'void TriassicPeriod()' is deprecated [-Wdeprecated-declarations]
   20 |     TriassicPeriod();
      |                    ^
<source>:4:6: note: declared here
    4 | void TriassicPeriod() {
      |      ^~~~~~~~~~~~~~
<source>:21:20: warning: 'void JurassicPeriod()' is deprecated: Use NeogenePeriod() instead. [-Wdeprecated-declarations]
   21 |     JurassicPeriod();
      |                    ^
<source>:9:6: note: declared here
    9 | void JurassicPeriod() {
      |      ^~~~~~~~~~~~~~
Compiler returned: 0

MSVC (x64 msvc v19.28 (VS16.9)) /std:c++14 /W4 :

<source>(20): warning C4996: 'TriassicPeriod': was declared deprecated
<source>(21): warning C4996: 'JurassicPeriod': Use NeogenePeriod() instead.
Compiler returned: 0

Avec C++20 vous pouvez activer conditionnellement les constructeurs facilement en utilisant un mot-clé requires .

#include <type_traits>

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

    [[deprecated("Attempting to use pointer type.")]]
    Foo() requires(std::is_pointer<T>::value) {}
};

int main()
{
    Foo<int> a;
    Foo<int*> b;
}

CCG (x86-64 gcc 10.3) --std=c++20 -Wall :

<source>: In function 'int main()':
<source>:12:14: warning: 'Foo<T>::Foo() requires  std::is_pointer<_Tp>::value [with T = int*]' is deprecated: Attempting to use pointer type. [-Wdeprecated-declarations]
   12 |   Foo<int *> b;
      |              ^
<source>:6:53: note: declared here
    6 |   [[deprecated("Attempting to use pointer type.")]] Foo() requires(
      |                                                     ^~~
Compiler returned: 0

MSVC (x64 msvc v19.28 (VS16.9)) /std:c++latest /W4 :

<source>(12): warning C4996: 'Foo<int *>::Foo': Attempting to use pointer type.
Compiler returned: 0

Si vous voulez utiliser quelque chose de similaire à static_warning(false, "Hello world"); Pour des raisons diverses, nous devons utiliser des macros. Voir Comment passer des chaînes de caractères brutes à l'attribut [[deprecated(message)]] ? par exemple.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X