Gibt es eine statische Warnung?

70

Mir ist diese Frage bekannt, in der Boosts "STATISCHE WARNUNG" erwähnt wird, aber ich möchte noch einmal speziell fragen, wie ich eine implementieren könnte, static_warningdie ähnlich funktioniert, static_assertaber nur zur Kompilierungszeit eine Warnung ausgibt, anstatt einen Kompilierungsfehler abzubrechen.

Ich hätte gerne etwas Ähnliches wie Alexandrescus Vorschlag für eine statische Zusicherung in C-11-Tagen vor C ++, die es irgendwie geschafft hat, einige nützliche Kontextinformationen als Teil des Fehlers zu drucken.

Es wäre akzeptabel zu verlangen, dass der Benutzer bestimmte Standard-Compiler-Warnungen aktiviert, damit diese Konstruktion funktioniert (möglicherweise "ungültige Zeigerkonvertierung" oder "Verstoß gegen strenge Aliasing-Regeln") - jede Warnung, die ohnehin Teil einer normalen Kompilierung sein sollte, kann dies verwendet werden.

Kurz gesagt, ich möchte static_warning(false, "Hello world");eine Compiler-Warnung erstellen, die irgendwie die Zeichenfolge "Hallo Welt" in die Warnmeldung aufnehmen sollte. Ist das möglich, etwa in GCC und MSVC, und wie?

Ich würde gerne eine kleine Belohnung für eine besonders clevere Lösung ausgeben.


Zur Erklärung: Ich kam auf die Idee, als ich über diese Frage nachdachte : Eine statische Warnung wäre eine nützliche Methode, um den Kompilierungsprozess komplexer Vorlagenspezialisierungen zu verfolgen, die ansonsten ziemlich schwer zu debuggen sind. Eine statische Warnung kann als einfaches Signal für den Compiler verwendet werden, um "Ich kompiliere jetzt diesen Teil des Codes" auszugeben.


Aktualisieren. Im Idealfall wird die Warnung im folgenden Setup ausgelöst:

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; }
Kerrek SB
quelle
Suchen Sie Portabilität? Ich weiß , dass einige Compiler Ähnliche Haken für den Prä - Prozessor implementieren ( #error, #warning, #message) so vielleicht , dass es Sinn machen würde , um tatsächlich die in gcc und Clang zu implementieren?
Matthieu M.
2
@VioletGiraffe: #warninghandelt von Präprozessor-Warnungen, soweit mir bekannt ist, und hat nichts mit Vorlageninstanziierungen zu tun.
Matthieu M.
2
Mit GCC kann das deprecatedAttribut auf Variablen, Typen und Funktionen angewendet werden. Dies kann eine beliebige Nachricht enthalten (siehe gcc.gnu.org/onlinedocs/gcc/Type-Attributes.html#Type-Attributes ). Ich habe versucht, eine Lösung damit zu hacken, aber bisher entziehen sich mir die Details. Es könnte jedoch eine praktikable Lösungskomponente sein.
Michael Ekstrand

Antworten:

51

Ausspielung von Michael E's Kommentar:

#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__)

Das Makro kann im Namespace, in der Struktur und im Funktionsumfang aufgerufen werden. Angesichts der Eingabe:

#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 (auf Standardwarnstufe) erzeugt:

static_warning.cpp: Im Konstruktor 'static_warning1 :: static_warning1 ()':
static_warning.cpp: 1: 1: Warnung: 'void static_warning1 :: _ (const detail :: false_type &)' 
    ist veraltet (deklariert bei static_warning.cpp: 1): Fehler mit 1 und 2 [-Wdeprecated-Deklarationen]
static_warning.cpp: Im Konstruktor 'Foo :: static_warning6 :: static_warning6 ()':
static_warning.cpp: 6: 3: Warnung: 'void Foo :: static_warning6 :: _ (const detail :: false_type &)'
    ist veraltet (deklariert bei static_warning.cpp: 6): 2 und 3: oops [-Wdeprecated-declarations]
static_warning.cpp: Im Konstruktor 'func () :: static_warning12 :: static_warning12 ()':
static_warning.cpp: 12: 3: Warnung: 'void func () :: static_warning12 :: _ (const detail :: false_type &)' 
    ist veraltet (deklariert bei static_warning.cpp: 12): Nicht so gut bei 3 und 4 [-Wdeprecated-Deklarationen]
static_warning.cpp: Im Konstruktor 'wrap <T> :: static_warning19 :: static_warning19 () [mit T = int]':
static_warning.cpp: 24: 17: von hier aus instanziiert
static_warning.cpp: 19: 3: Warnung: 'void wrap <T> :: static_warning19 :: _ (const detail :: false_type &) [mit T = int]' 
    ist veraltet (deklariert bei static_warning.cpp: 19): Bad mit 4 und 5 [-Wdeprecated-Deklarationen]

Während Visual C ++ 2010 (bei / W3 oder höher) sagt:

warnproj.cpp (1): Warnung C4996: 'static_warning1 :: _': Fehler mit 1 und 2
warnproj.cpp (1): siehe Deklaration von 'static_warning1 :: _'
warnproj.cpp (6): Warnung C4996: 'Foo :: static_warning6 :: _': 2 und 3: oops
warnproj.cpp (6): siehe Deklaration von 'Foo :: static_warning6 :: _'
warnproj.cpp (12): Warnung C4996: 'func :: static_warning12 :: _': Nicht so gut bei 3 und 4
warnproj.cpp (12): siehe Deklaration von 'func :: static_warning12 :: _'
warnproj.cpp (19): Warnung C4996: 'wrap <T> :: static_warning19 :: _': Schlecht mit 4 und 5
    mit
    [
        T = int
    ]]
warnproj.cpp (19): siehe Deklaration von 'wrap <T> :: static_warning19 :: _'
    mit
    [
        T = int
    ]]
warnproj.cpp (19): Beim Kompilieren der Klassenvorlagenelementfunktion 'wrap <T> :: static_warning19 :: static_warning19 (void)'
    mit
    [
        T = int
    ]]
warnproj.cpp (24): Siehe Verweis auf die Instanziierung der Klassenvorlage 'wrap <T> :: static_warning19', die kompiliert wird
    mit
    [
        T = int
    ]]

Clang ++ 3.1 unter Linux erzeugt die wohl schönere Ausgabe (Farbe nicht gezeigt):

tst3.cpp: 1: 1: Warnung: '_' ist veraltet: Fehlgeschlagen mit 1 und 2
      [Veraltete Erklärungen]
STATIC_WARNING (1 == 2, "Fehlgeschlagen mit 1 und 2");
^
tst3.cpp: 24: 38: Hinweis: erweitert vom Makro 'STATIC_WARNING'
  PP_CAT (static_warning, __ LINE __) () {_ (:: detail :: converter <(cond)> ());} \
                                     ^
tst3.cpp: 6: 3: Warnung: '_' ist veraltet: 2 und 3: oops
      [Veraltete Erklärungen]
  STATIC_WARNING (2 == 3, "2 und 3: oops");
  ^
tst3.cpp: 24: 38: Hinweis: erweitert vom Makro 'STATIC_WARNING'
  PP_CAT (static_warning, __ LINE __) () {_ (:: detail :: converter <(cond)> ());} \
                                     ^
tst3.cpp: 12: 3: Warnung: '_' ist veraltet: Nicht so gut bei 3 und 4
      [Veraltete Erklärungen]
  STATIC_WARNING (3 == 4, "Nicht so gut bei 3 und 4");
  ^
tst3.cpp: 24: 38: Hinweis: erweitert vom Makro 'STATIC_WARNING'
  PP_CAT (static_warning, __ LINE __) () {_ (:: detail :: converter <(cond)> ());} \
                                     ^
tst3.cpp: 19: 3: Warnung: '_' ist veraltet: Schlecht mit 4 und 5
      [Veraltete Erklärungen]
  STATIC_WARNING (4 == 5, "Schlecht mit 4 und 5");
  ^
tst3.cpp: 24: 38: Hinweis: erweitert vom Makro 'STATIC_WARNING'
  PP_CAT (static_warning, __ LINE __) () {_ (:: detail :: converter <(cond)> ());} \
                                     ^
tst3.cpp: 23: 17: Hinweis: zur Instanziierung der Mitgliedsfunktion
      'wrap <int> :: static_warning19 :: static_warning19' wird hier angefordert
Template Struct Wrap <int>
                ^
4 Warnungen generiert.
Managu
quelle
1
Dies ist eine sehr interessante Lösung, und es ist großartig, dass Sie wörtliche Warnungen drucken können. Es ist jedoch seltsam, dass Sie nur in explizit instanziierten Vorlagen verwenden können, während während der impliziten Instanziierung ( wrap<char> w;) keine Warnung ausgegeben wird . Kann das überwunden werden?
Kerrek SB
2
Verdammt, das war genau die Antwort, die ich schreiben wollte, als ich die Frage sah.
Flexo
1
@KerrekSB: Hinzugefügt STATIC_WARNING_TEMPLATE, um Ihr Problem zu beheben , jedoch mit erheblichen Kosten: Durch die Verwendung von STATIC_WARNING_TEMPLATE wird die Bedeutung eines Programms durch Hinzufügen einer Variablen (mit einem eigenen Standardkonstruktor) geändert.
Managu
1
Und das tut immer noch nichts für z Foo<int>::value. Ich habe noch nichts herausgefunden, was wird.
Managu
1
@ Managu: Danke, das ist großartig! Die Kosten sollten nicht zu wichtig sein, wenn dies zum Debuggen von Vorlageninstanziierungen verwendet wird.
Kerrek SB
14

Hier ist das Beste, was ich mir bisher ausgedacht habe. Es ist grundlegend und entspricht nicht ganz Ihren Anforderungen. Stattdessen muss BOOST_MPL_ASSERT_MSGIhre Nachricht die Form einer gültigen Kennung haben. (Soweit ich weiß, können Sie eine Zeichenfolge nur dann in die Warnmeldung drucken lassen, wenn die von Ihnen verwendete Warnung zufällig auch mit Zeichenfolgen zu tun hat und deren Inhalt gedruckt wird.)

Die Warnung für eine nicht verwendete Variable muss aktiviert sein. In g ++ ist dies -Wunused-variable(aktiviert durch -Wall) und in MSVC ist es die Warnung C4101, die auf Warnungsebene 3 aktiviert ist.

Es ist offensichtlich nicht sehr getestet und könnte auf einige Arten verbessert werden (Verwendung __COUNTER__anstelle von __LINE__unterstützten Compilern, hübscheres Drucken von Nachrichten, Verwenden von Boost zur Vereinfachung usw.), scheint aber die Aufgabe zu erledigen. Hier ist die Kesselplatte:

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__)

Und ein Test:

STATIC_WARNING(sizeof(int) == 2);

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

In MSVC erzeugt dies:

>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

Und in GCC produziert es:

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'
GManNickG
quelle
3
Dies ist sehr interessant, scheint aber nur in einer freien Funktion oder einem globalen Kontext zu funktionieren. Ich kann keine Warnungen aus einer Klassenvorlage drucken lassen ...
Kerrek SB
4

Hier ist eine Lösung, die die Boost MPL-Bibliothek verwendet:

#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__)

Es kommt mit der gleichen Einschränkung wie die Lösung von GMan: Die Nachricht muss eine gültige Kennung sein. Hier sind zwei Tests

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

und

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

Mit MSVS 2010 wird der erste Test ohne Warnungen kompiliert, der zweite mit der Warnung

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>
    ]

Der Code verwendet boost :: mpl :: print. Aus dem Buch C ++ Template Metaprogramming von D. Abrahams und A. Gurtovoy, Seite 171:

Um ein Ausführungsprotokoll zur Kompilierungszeit zu erstellen, benötigen wir eine Möglichkeit, eine Diagnosemeldung zu generieren - eine Warnung. Da es kein einzelnes Konstrukt gibt, das alle Compiler veranlasst, eine Warnung zu generieren (tatsächlich lassen Sie die meisten Compiler Warnungen insgesamt deaktivieren), verfügt MPL über eine printMetafunktion, die genau so ist, identitydass sie so optimiert ist, dass eine Warnung für eine Vielzahl gängiger Compiler generiert wird ihre üblichen Einstellungen.

Johan Råde
quelle
Ich denke du vermisst #include <boost/mpl/eval_if.hpp>und auch typenamevor dem boost::eval_if_c. Wie auch immer, ich kann damit nichts drucken (GCC 4.6.2); Die Kompilierung wird nur ohne Nachricht durchgeführt ...
Kerrek SB
Ich habe das fehlende #include <boost / mpl / eval_if.hpp> und den fehlenden Typnamen behoben. Wenn der Code mit GCC 4.6.2 keine Warnungen generiert, ist dies wahrscheinlich ein Boost :: mpl :: print-Fehler. Welche Boost-Version verwenden Sie?
Johan Råde
Was passiert unter GCC 4.6.2, wenn Sie #include <boost / mpl / print.hpp> NEWLINE boost :: mpl :: print <int> :: type a kompilieren?
Johan Råde