在 C++ 中,static 断言当前位置的声明不会包含在任何名称空间中?

In C++, static assert that a declaration at current position would not be enclosed in any namespace?

在 C++ 中,有时会出现恼人的错误,即有人忘记关闭在 header 文件中打开的命名空间,有时很难准确找到是哪一个。

不太平庸,有时有些宏出于技术原因不能出现在任何命名空间中,或者可能会出现神秘的错误消息。例如,在 boost 融合库中,BOOST_FUSION_DEFINE_STRUCT 需要这个。相反,您应该在文件范围内使用宏,并将它传递给您希望声明所在的命名空间。

就此而言,如果您在命名空间中包含任何标准库 header,则会出现未定义的行为。

是否可以做一些事情,比如写一个宏,例如ASSERT_FILESCOPE 这样我就可以在 C++ header 文件的末尾放一行 ASSERT_FILESCOPE;,或者在像 BOOST_FUSION_DEFINE_STRUCT 这样的宏的开头,这将导致 static_assert 如果该表达式在文件范围内 not,则失败并显示一条很好的错误消息?

一种可能性是声明一个名称,然后测试 ::name。为避免在稍后在同一翻译单元中使用宏之前在全局范围内声明相同的名称,您可以使用 __COUNTER__,或者 __LINE__(如果这样更适合您)。无论如何它都不完美,但至少错误的第一行包含消息。 live example

#include <type_traits>

#define CONCAT(x, y) CONCAT_I(x, y)
#define CONCAT_I(x, y) x##y

#define ASSERT_FILESCOPE \
    ASSERT_FILESCOPE_I(__COUNTER__)

#define ASSERT_FILESCOPE_I(counter) \
    static constexpr bool CONCAT(ASSERTION_FAILED_NOT_AT_FILE_SCOPE, counter)() { return true; } \
    static_assert(::CONCAT(ASSERTION_FAILED_NOT_AT_FILE_SCOPE, counter)(), "Detected a location other than file scope.")

我从 Clang 得到这个错误:

main.cpp:17:5: error: no member named 'ASSERTION_FAILED_NOT_AT_FILE_SCOPE0' in the global namespace; did you mean simply 'ASSERTION_FAILED_NOT_AT_FILE_SCOPE0'?
    ASSERT_FILESCOPE;
    ^~~~~~~~~~~~~~~~
main.cpp:7:5: note: expanded from macro 'ASSERT_FILESCOPE'
    ASSERT_FILESCOPE_I(__COUNTER__)
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.cpp:11:19: note: expanded from macro 'ASSERT_FILESCOPE_I'
    static_assert(::CONCAT(ASSERTION_FAILED_NOT_AT_FILE_SCOPE, counter)(), "Detected a location other than file scope.")
                  ^~
main.cpp:17:5: note: 'ASSERTION_FAILED_NOT_AT_FILE_SCOPE0' declared here
main.cpp:7:5: note: expanded from macro 'ASSERT_FILESCOPE'
    ASSERT_FILESCOPE_I(__COUNTER__)
    ^
main.cpp:10:20: note: expanded from macro 'ASSERT_FILESCOPE_I'
    static constexpr bool CONCAT(ASSERTION_FAILED_NOT_AT_FILE_SCOPE, counter)() { return true; } \
                   ^
main.cpp:3:22: note: expanded from macro 'CONCAT'
#define CONCAT(x, y) CONCAT_I(x, y)
                     ^
main.cpp:4:24: note: expanded from macro 'CONCAT_I'
#define CONCAT_I(x, y) x##y
                       ^
<scratch space>:101:1: note: expanded from here
ASSERTION_FAILED_NOT_AT_FILE_SCOPE0
^

这一个来自 GCC:

main.cpp:11:19: error: '::ASSERTION_FAILED_NOT_AT_FILE_SCOPE0' has not been declared
     static_assert(::CONCAT(ASSERTION_FAILED_NOT_AT_FILE_SCOPE, counter)(), "Detected a location other than file scope.")
                   ^
main.cpp:7:5: note: in expansion of macro 'ASSERT_FILESCOPE_I'
     ASSERT_FILESCOPE_I(__COUNTER__)
     ^
main.cpp:17:5: note: in expansion of macro 'ASSERT_FILESCOPE'
     ASSERT_FILESCOPE;
     ^
main.cpp:11:19: note: suggested alternative:
     static_assert(::CONCAT(ASSERTION_FAILED_NOT_AT_FILE_SCOPE, counter)(), "Detected a location other than file scope.")
                   ^
main.cpp:7:5: note: in expansion of macro 'ASSERT_FILESCOPE_I'
     ASSERT_FILESCOPE_I(__COUNTER__)
     ^
main.cpp:17:5: note: in expansion of macro 'ASSERT_FILESCOPE'
     ASSERT_FILESCOPE;
     ^
main.cpp:10:34: note:   'foo::ASSERTION_FAILED_NOT_AT_FILE_SCOPE0'
     static constexpr bool CONCAT(ASSERTION_FAILED_NOT_AT_FILE_SCOPE, counter)() { return true; } \
                                  ^
main.cpp:4:24: note: in definition of macro 'CONCAT_I'
 #define CONCAT_I(x, y) x##y
                        ^
main.cpp:10:27: note: in expansion of macro 'CONCAT'
     static constexpr bool CONCAT(ASSERTION_FAILED_NOT_AT_FILE_SCOPE, counter)() { return true; } \
                           ^
main.cpp:7:5: note: in expansion of macro 'ASSERT_FILESCOPE_I'
     ASSERT_FILESCOPE_I(__COUNTER__)
     ^
main.cpp:17:5: note: in expansion of macro 'ASSERT_FILESCOPE'
     ASSERT_FILESCOPE;
     ^

这是一个将我最初想法的一部分与@chris 的想法结合在一起的版本。目前尚不清楚它是否真的更好,除了在它失败时实际触发静态断言,而且代价是稍微复杂一些。 YMMV:

// Fall through to this value if we can't see the test symbol
template <int N>
struct assert_filescope_check_ {
  static constexpr bool value = false;
};

#define ASSERT_FILESCOPE_CONCAT(x, y) ASSERT_FILESCOPE_CONCAT_I(x, y)
#define ASSERT_FILESCOPE_CONCAT_I(x, y) x##y

#define ASSERT_FILESCOPE \
    ASSERT_FILESCOPE_I(__COUNTER__)

#define  ASSERT_FILESCOPE_I(counter)                                  \
    template <int> struct assert_filescope_check_;                    \
                                                                      \
    template <>                                                       \
    struct assert_filescope_check_<counter> {                         \
      static constexpr bool value = true;                             \
    };                                                                \
                                                                      \
    static_assert(::assert_filescope_check_<counter>::value,          \
                  "Assert filescope failed!")

主要思想是,在文件范围的 header 中定义了一个模板,我们尝试使用 counter 的值对其进行部分专门化。如果我们然后可以看到偏特化,那么我们就通过了。在我们尝试部分特化之前,我们必须重新声明主模板(但不定义它),以便在我们实际上位于命名空间内的情况下,我们最终只定义了一个新符号。

当我第一次尝试时,我想避免使用 __COUNTER____LINE__ 并为此使用模板技巧,但这使它变得太复杂了。而且我想无论如何它是 well-understood 如何做到这一点,只是有点乏味。 1 2.

如果您需要避免 __COUNTER__ 并且遇到 __LINE__ 的问题,那么我想您可以将这些解决方案之一放入 @chris' 或我的版本中,代替 __COUNTER__实例。所以,没有损失。 :)