84

I'm aware of this question which mentions Boost's "STATIC WARNING", but I'd like to ask again, specifically, how I could implement a static_warning which operates similarly to static_assert but only emits a warning at compile time rather than an aborting compilation error.

I'd like something similar to Alexandrescu's proposal for a static assert in pre-C++11 days which somehow managed to print some useful contextual information as part of the error.

It would be acceptable to require that the user enable certain standard compiler warnings in order for this construction to work (perhaps "invalid pointer conversion" or "breaks strict aliasing rules") -- any warning that should be part of a normal compilation anyway can be used.

In short, I want static_warning(false, "Hello world"); to create a compiler warning that should somehow include the string "hello world" in the warning message. Is this possible, say in GCC and MSVC, and how?

I'd happily give out a small reward bounty for any particularly clever solution.


As a bit of explanation: I got the idea when thinking about this question: A static warning would be a useful way to trace through the compile-time process of complex template specializations, which are otherwise fairly hard to debug. A static warning could be used as a simple beacon for the compiler to emit "I'm now compiling this part of the code."


Update. Ideally, the warning would be triggered in the following setup:

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; }
Community
  • 1
  • 1
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • Are you looking for portability ? I know that some compilers implement similar hooks for the preprocessor (`#error`, `#warning`, `#message`) so perhaps that it would make sense to actually implement those in gcc and Clang ? – Matthieu M. Jan 20 '12 at 08:22
  • 2
    @VioletGiraffe: `#warning` is about preprocessor warnings as far as I am aware of, and has nothing to do with templates instanciations. – Matthieu M. Jan 20 '12 at 08:23
  • 2
    GCC allows the `deprecated` attribute to be applied to variables, types, and functions; this can include an arbitrary message (see http://gcc.gnu.org/onlinedocs/gcc/Type-Attributes.html#Type-Attributes). I've tried hacking up a solution using it, but so far the details elude me; it might be a viable solution component, though. – Michael Ekstrand Jan 23 '12 at 03:49

4 Answers4

56

Playing off of Michael E's comment:

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

The macro can be invoked at namespace, structure, and function scope. Given the input:

#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 (at default warning level) produces:

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]

While Visual C++ 2010 (at /W3 or above) says:

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 on Linux produces the arguably nicer output (color not shown):

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.
Managu
  • 8,849
  • 2
  • 30
  • 36
  • Why aren't you using the gcc attribute `warning`? – PlasmaHH Jan 24 '12 at 16:29
  • @PlasmaHH: From the GCC texinfo: `warning ("message")`: "If this attribute is used on a function declaration and a call to such a function is not eliminated through ... optimizations, a warning which will include `message` will be diagnosed." I didn't want optimization to be a concern. After all, the STATIC_WARNING(...) function calls are designed to be entirely optimized away. – Managu Jan 24 '12 at 16:37
  • Ah, weird, I was under the impression that warning and deprecated had the same requirements there and just different message. – PlasmaHH Jan 24 '12 at 17:08
  • 1
    This is a very interesting solution, and it's great that you can print literal warnings. It's strange though that you can only use in *explicitly* instantiated templates, while no warning is produced during implicit instantiation (`wrap w;`). Can that be overcome? – Kerrek SB Jan 30 '12 at 16:08
  • 2
    Damn this was exactly the answer I was going to write when I saw the question. – Flexo Jan 30 '12 at 20:43
  • @KerrekSB: I spent quite a while trying. I couldn't get _any_ warning out of an implicitly instantiated template with either GCC or MSVC++10 at default warning level. – Managu Jan 31 '12 at 02:57
  • @KerrekSB: As to your update: you could always move the STATIC_WARNINGs into `Foo`s default constructor. It's not ideal, but I think it would work. – Managu Jan 31 '12 at 15:39
  • @Managu: I think I tried that too, and that didn't work with the present code. – Kerrek SB Jan 31 '12 at 15:45
  • 1
    @KerrekSB: Added `STATIC_WARNING_TEMPLATE`, which should address your issue, but at a significant cost: using STATIC_WARNING_TEMPLATE changes the meaning of a program by adding in a variable (with its own default constructor). – Managu Jan 31 '12 at 16:27
  • 1
    And this still doesn't do anything for, e.g. `Foo::value`. Haven't figured out anything yet which will. – Managu Jan 31 '12 at 16:35
  • 1
    @Managu: Thanks, this is great! The cost shouldn't be too important when this is used for debugging template instantiations. – Kerrek SB Jan 31 '12 at 16:46
  • the `token` bit is to avoid runnning afoul of the ODR if `STATIC_WARNING_TEMPLATE` were to be used at global scope. If `STATIC_WARNING_TEMPLATE` is never used at global scope, this shouldn't be an issue; `token` can be removed from the macro, and the last bit could read `PP_CAT(_localvar,__LINE__)` instead of `PP_CAT(PP_CAT(_localvar,token),__LINE__)` – Managu Jan 31 '12 at 16:55
  • Somehow msvc only warns about first instantation in case if both trigger the same warning. Works correctly with gcc though https://gcc.godbolt.org/z/EK2zNg edit: nvm, looks like it has this problem with static_assert too – Predelnik Dec 28 '18 at 07:31
14

Here's the best I've come up with so far. It's basic and doesn't quite fit your requirements, but instead goes the route of BOOST_MPL_ASSERT_MSG in that your message has to take the form of a valid identifier. (As far as I know, the only way you could get a string printed in the warning message is if the warning you used happened to also be one to do with strings, and printed its contents.)

It requires the warning for an unused variable be enabled. In g++ this is -Wunused-variable (enabled by -Wall), and in MSVC it's warning C4101 which is enabled at Warning Level 3.

It's obviously not very tested and could be enhanced in a few ways (use __COUNTER__ instead of __LINE__ on supported compilers, prettier message printing, use Boost to simplify, etc.), but seems to get the job done. Here's the boiler-plate:

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

And a test:

STATIC_WARNING(sizeof(int) == 2);

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

In MSVC this produces:

>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

And in GCC it produces:

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
  • 494,350
  • 52
  • 494
  • 543
  • 3
    This is very interesting, but it only appears to work in a free function or global context. I can't make it print any warnings from inside a class template... – Kerrek SB Jan 30 '12 at 15:59
4

Here is a solution that uses the Boost MPL library:

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

It comes with the same restriction as GMan's solution: the message must be a valid identifier. Here are two tests

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

and

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

With MSVS 2010 the first test compiles without warnings, the second compiles with the warning

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

The code uses boost::mpl::print. From the book C++ Template Metaprogramming by D. Abrahams and A. Gurtovoy, page 171:

To generate a compile-time execution log, we'd need a way to generate a diagnostic message - a warning. Because there's no single construct that will cause all compilers to generate a warning (indeed, most compilers let you disable warnings altogether), MPL has a print metafunction that is just like identity except that it is tuned to generate a warning on a variety of popular compilers with their usual settings.

Johan Råde
  • 20,480
  • 21
  • 73
  • 110
  • I think you're missing `#include `, and also `typename` in front of the `boost::eval_if_c`. Anyway, I can't get this to print anything (GCC 4.6.2); compilation just goes through without any message at all... – Kerrek SB Jan 30 '12 at 16:14
  • I have fixed the missing #include and the missing typename. If the code does not generate any warnings with GCC 4.6.2, then that is probably a boost::mpl::print bug. What Boost version are you using? – Johan Råde Jan 30 '12 at 20:35
  • What happens on GCC 4.6.2 if you compile #include NEWLINE boost::mpl::print::type a; – Johan Råde Jan 30 '12 at 20:44
3

Since C++14, you can use attribute [[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();
}
GCC (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

With C++20, you can conditionally enable constructors easily using keyword 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;
}
GCC (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

If you want to use something similar to static_warning(false, "Hello world");, we have to use macros for various reasons. See How to pass raw string literals to [[deprecated(message)]] attribute? for example.

Burak
  • 2,251
  • 1
  • 16
  • 33
  • It can have a comment as well `[[deprecated("warning, this is a slow function")]]`. – alfC Apr 27 '21 at 18:56