是否存在static_warning?


70

我知道这个问题提到了Boost的“ STATIC WARNING”,但是我想再次提一个问题,特别是我如何实现一个static_warning操作类似于a的操作,static_assert但仅在编译时发出警告,而不是中止编译错误。

我想要类似于Alexandrescu提出的在C ++ 11之前的静态断言的提议,该提议以某种方式设法打印了一些有用的上下文信息作为错误的一部分。

要求用户启用某些标准的编译器警告以使此构造起作用(可能是“无效的指针转换”或“违反严格的别名规则”),这是可以接受的-无论如何,任何应作为常规编译一部分的警告都可以使用。

简而言之,我想static_warning(false, "Hello world");创建一个编译器警告,该警告应以某种方式在警告消息中包含字符串“ hello world”。在GCC和MSVC中,这可能吗?如何?

对于任何特别聪明的解决方案,我都会很高兴地给予小额奖励。


作为一点解释:在考虑这个问题时,我想到了一个主意:静态警告将是跟踪复杂模板专业化的编译时过程的有用方法,否则很难调试。静态警告可以用作编译器发出“我正在编译这部分代码”的简单信标。


更新。理想情况下,将在以下设置中触发警告:

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; }

您在寻找便携性吗?我知道有些编译器为预处理器(#error,, )实现了类似的钩子#warning#message因此也许在gcc和Clang中实现这些钩子有意义吗?
Matthieu M. 2012年

2
@VioletGiraffe:#warning据我所知,它与预处理器警告有关,与模板实例化无关。
Matthieu M. 2012年

2
GCC允许将deprecated属性应用于变量,类型和函数;这可以包含任意消息(请参阅gcc.gnu.org/onlinedocs/gcc/Type-Attributes.html#Type-Attributes)。我已经尝试过使用它来破解一个解决方案,但是到目前为止,细节仍然使我望而却步。不过,它可能是可行的解决方案组件。
Michael Ekstrand

Answers:


51

淡化迈克尔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__)

可以在名称空间,结构和功能范围内调用该宏。给定输入:

#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(默认警告级别)产生:

static_warning.cpp:在构造函数“ static_warning1 :: static_warning1()”中:
static_warning.cpp:1:1:警告:'无效static_warning1 :: __(const detail :: false_type&)' 
    被弃用(在static_warning.cpp:1处声明):失败,出现1和2 [-Wdeprecated-declarations]
static_warning.cpp:在构造函数“ Foo :: static_warning6 :: static_warning6()”中:
static_warning.cpp:6:3:警告:'void Foo :: static_warning6 :: __(const detail :: false_type&)'
    已弃用(在static_warning.cpp:6处声明):2和3:oops [-Wdeprecated-declarations]
static_warning.cpp:在构造函数'func():: static_warning12 :: static_warning12()'中:
static_warning.cpp:12:3:警告:'void func():: static_warning12 :: _(const detail :: false_type&)' 
    不推荐使用(在static_warning.cpp:12声明):在3和4上不太好[-Wdeprecated-declarations]
static_warning.cpp:在构造函数'wrap <T> :: static_warning19 :: static_warning19()[with T = int]'中:
static_warning.cpp:24:17:从此处实例化
static_warning.cpp:19:3:警告:“无效包装<T> :: static_warning19 :: _(const detail :: false_type&)[with T = int]” 
    被弃用(在static_warning.cpp:19处声明):不好,包含4和5 [-Wdeprecated-declarations]

虽然Visual C ++ 2010(/ W3或更高版本)说:

warnproj.cpp(1):警告C4996:'static_warning1 :: _':失败为1和2
warnproj.cpp(1):请参见“ static_warning1 :: _”的声明
warnproj.cpp(6):警告C4996:'Foo :: static_warning6 :: _':2和3:哎呀
warnproj.cpp(6):参见'Foo :: static_warning6 :: _'的声明
warnproj.cpp(12):警告C4996:'func :: static_warning12 :: _':在3和4上不太好
warnproj.cpp(12):参见'func :: static_warning12 :: _'的声明
warnproj.cpp(19):警告C4996:'wrap <T> :: static_warning19 :: _':4和5错误
    与
    [
        T =整数
    ]
warnproj.cpp(19):参见'wrap <T> :: static_warning19 :: _'的声明
    与
    [
        T =整数
    ]
warnproj.cpp(19):编译类模板成员函数'wrap <T> :: static_warning19 :: static_warning19(void)'时
    与
    [
        T =整数
    ]
warnproj.cpp(24):请参见对正在编译的类模板实例化'wrap <T> :: static_warning19'的引用
    与
    [
        T =整数
    ]

Linux上的Clang ++ 3.1产生了更好的输出(未显示颜色):

tst3.cpp:1:1:警告:不推荐使用'_':失败1和2
      [-Wdeprecated-clarations]
STATIC_WARNING(1 == 2,“失败的1和2”);
^
tst3.cpp:24:38:注意:从宏“ STATIC_WARNING”扩展
  PP_CAT(static_warning,__ LINE __)(){_(:: detail :: converter <(cond)>());} \
                                     ^
tst3.cpp:6:3:警告:不推荐使用'_':2和3:哎呀
      [-Wdeprecated-clarations]
  STATIC_WARNING(2 == 3,“ 2 and 3:oops”);
  ^
tst3.cpp:24:38:注意:从宏“ STATIC_WARNING”扩展
  PP_CAT(static_warning,__ LINE __)(){_(:: detail :: converter <(cond)>());} \
                                     ^
tst3.cpp:12:3:警告:不推荐使用'_':在3和4上效果不佳
      [-Wdeprecated-clarations]
  STATIC_WARNING(3 == 4,“在3和4上不太好”);
  ^
tst3.cpp:24:38:注意:从宏“ STATIC_WARNING”扩展
  PP_CAT(static_warning,__ LINE __)(){_(:: detail :: converter <(cond)>());} \
                                     ^
tst3.cpp:19:3:警告:不推荐使用'_':4和5错误
      [-Wdeprecated-clarations]
  STATIC_WARNING(4 == 5,“错误的4和5”);
  ^
tst3.cpp:24:38:注意:从宏“ STATIC_WARNING”扩展
  PP_CAT(static_warning,__ LINE __)(){_(:: detail :: converter <(cond)>());} \
                                     ^
tst3.cpp:23:17:注意:在成员函数的实例化中
      这里要求'wrap <int> :: static_warning19 :: static_warning19'
模板struct wrap <int>
                ^
生成4条警告。

1
这是一个非常有趣的解决方案,很高兴您可以打印文字警告。奇怪的是,您只能在显式实例化的模板中使用,而隐式实例化(wrap<char> w;)期间不会产生警告。可以克服吗?
Kerrek SB 2012年

2
该死的,这正是我看到问题时要写的答案。
Flexo

1
@KerrekSB:已添加STATIC_WARNING_TEMPLATE,可以解决您的问题,但要付出高昂的代价:使用STATIC_WARNING_TEMPLATE通过添加变量(具有其自己的默认构造函数)来更改程序的含义。
Managu

1
而且这仍然无济于事,例如Foo<int>::value。还没有想出什么会的。
Managu

1
@Managu:谢谢,太好了!将其用于调试模板实例化时,成本应该不太重要。
Kerrek SB 2012年

14

这是到目前为止我想出的最好的。它是基本的,并不完全符合您的要求,但是走的路要走,BOOST_MPL_ASSERT_MSG因为您的消息必须采用有效标识符的形式。(据我所知,您可以在警告消息中打印字符串的唯一方法是,您使用的警告是否也与字符串有关,并打印其内容。)

它要求启用未使用变量的警告。在g ++中,这是-Wunused-variable(由启用的-Wall),而在MSVC中,它是警告C4101,其在警告级别3处启用。

它显然还没有经过很好的测试,可以通过几种方式进行增强(__COUNTER__代替__LINE__在支持的编译器上使用,更漂亮的消息打印,使用Boost来简化等),但是似乎可以完成工作。这是样板:

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

并进行测试:

STATIC_WARNING(sizeof(int) == 2);

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

在MSVC中,这会产生:

>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

在GCC中产生:

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
这是非常有趣的,但是它似乎只能在自由功能或全局上下文中工作。我无法使其从班级模板中打印任何警告...
Kerrek SB 2012年

4

这是使用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__)

它具有与GMan解决方案相同的限制:消息必须是有效的标识符。这是两个测试

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

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

使用MSVS 2010,第一个测试在没有警告的情况下进行编译,第二个测试在没有警告的情况下进行编译

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

该代码使用boost :: mpl :: print。摘自D.Abrahams和A.Gurtovoy的书《C ++模板元编程》,第171页:

要生成编译时执行日志,我们需要一种生成诊断消息的方法-警告。因为没有一个单一的结构会导致所有编译器生成警告(实际上,大多数编译器都允许您完全禁用警告),所以MPL具有与之print类似的元函数,只是identity它被调整为在各种流行的编译器上生成警告。他们通常的设置。


我想你失踪了#include <boost/mpl/eval_if.hpp>,还在typename眼前boost::eval_if_c。无论如何,我无法打印任何内容(GCC 4.6.2);编译过程完全没有任何消息...
Kerrek SB 2012年

我已经修复了丢失的#include <boost / mpl / eval_if.hpp>和丢失的类型名。如果代码在GCC 4.6.2中未生成任何警告,则可能是boost :: mpl :: print错误。您正在使用什么Boost版本?
约翰·拉德(JohanRåde)2012年

如果编译#include <boost / mpl / print.hpp> NEWLINE boost :: mpl :: print <int> :: type a,在GCC 4.6.2上会发生什么?
约翰·拉德(JohanRåde)2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.