【问题标题】:How could my code tell a compile-time constant versus a variable?我的代码如何区分编译时常量和变量?
【发布时间】:2011-10-19 19:50:33
【问题描述】:

这是我的问题。我有一个BINARY_FLAG 宏:

#define BINARY_FLAG( n ) ( static_cast<DWORD>( 1 << ( n ) ) )

可以像这样使用(“恒定”场景):

static const SomeConstant = BINARY_FLAG( 5 );

或像这样(“可变”场景):

for( int i = 0; i < 10; i++ ) {
    DWORD flag = BINARY_FLAG( i );
    // do something with the value
}

这个宏根本不是万无一失的——你可以在那里传递-134,最多会有一个警告,但行为是不确定的。我想让它更万无一失。

对于恒定的场景,我可以使用模板:

template<int Shift> class BinaryFlag {
staticAssert( 0 <= Shift && Shift < sizeof( DWORD) * CHAR_BIT );
public:
static const DWORD FlagValue = static_cast<DWORD>( 1 << Shift );
};
#define BINARY_FLAG( n ) CBinaryFlag<n>::FlagValue

但这不适用于“变量”场景 - 我需要一个运行时断言:

inline DWORD ProduceBinaryFlag( int shift )
{
    assert( 0 <= shift && shift < sizeof( DWORD) * CHAR_BIT );
    return static_cast<DWORD>( 1 << shift );
}
#define BINARY_FLAG( n ) ProduceBinaryFlag(n)

后者很好,但没有编译时检查。当然,我希望在可能的情况下进行编译时检查,否则进行运行时检查。在任何时候,我都希望尽可能少的运行时开销,所以当编译时检查可能时,我不想要函数调用(可能不会被内联)。

我看到this question,但看起来不是同一个问题。

是否有一些结构可以根据作为标志号传递的表达式是编译时常量还是变量来在两者之间交替?

【问题讨论】:

  • assert( 0 &lt;= flag &amp;&amp; flag &lt; 32 )... 你的意思是flag ==means==> shift 这里吗?另外,如果您在运行时将争论传递给ProduceBinaryFlag(),那么您如何在编译时进行检查?还是我错过了什么?
  • 另一个例子:const int myflag = 5; BINARY_FLAG(myflag);myflag 是编译时常量,但不是文字。问题中的示例仅使用编译时常量的一种特殊情况。
  • 一个想法 - 如果你愿意假设一个支持 VLA 的 C++ 实现,那么宏可以首先进行运行时检查,然后如果 n 在范围内,则声明一个大小为正的数组否则为负。在n 是整数常量表达式的情况下,如果表示大小的表达式也是 ICE,这将在编译时失败。在n 不是常量的情况下,UB 将被运行时检查阻止。由于该阵列很小且未使用,因此希望它不会产生巨大的成本。
  • @iammilind:我修复了代码。好吧,是的,如果我将它传递给一个函数,它就不再是编译时检查,这就是我想要避免的。
  • 在 C++0x 实现的当前状态下,这完全不可能。当编译器实现constexpr 时,您将有办法。与此同时,我不明白为什么你的宏不正常。

标签: c++ templates visual-c++ macros metaprogramming


【解决方案1】:

这比你想象的要简单:)

让我们看看:

#include <cassert>

static inline int FLAG(int n) {
    assert(n>=0 && n<32);
    return 1<<n;
}

int test1(int n) {
    return FLAG(n);
}
int test2() {
    return FLAG(5);
}

我不使用 MSVC,但我使用 Mingw GCC 4.5 编译:

g++ -c -S -O3 08042.cpp

第一种方法的结果代码如下所示:

__Z5test1i:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $24, %esp
    movl    8(%ebp), %ecx
    cmpl    $31, %ecx
    ja  L4
    movl    $1, %eax
    sall    %cl, %eax
    leave
    ret
L4:
    movl    $4, 8(%esp)
    movl    $LC0, 4(%esp)
    movl    $LC1, (%esp)
    call    __assert
    .p2align 2,,3

第二个:

__Z5test2v:
    pushl   %ebp
    movl    %esp, %ebp
    movl    $32, %eax
    leave
    ret

看到了吗?编译器足够聪明,可以为您完成。不需要宏,不需要元编程,不需要 C++0x。就这么简单。

检查 MSVC 是否做同样的事情......但是看看 - 编译器很容易评估一个常量表达式并删除未使用的条件分支。如果你想确定的话,检查它...... . 但总的来说 - 相信你的工具。

【讨论】:

  • 这很好,除非我不会因为肯定是非法的用例而出现编译时错误。运行时错误只会发生在某些数据集上,编译时会立即发生编译时错误。
  • 是的。该静态断言是如何实现的?有了上面的内容,我们所需要的只是一个静态断言,如果它的参数在编译时不能被评估,它会默默地编译成空。
  • 可能是不可能的,因为模板参数可以是类型名或常量表达式...嗯。这需要诡计!
【解决方案2】:

不可能将参数传递给宏或函数并确定它是编译时常量还是变量。

最好的方法是你 #define BINARY_FLAG(n) 使用编译时代码并将该宏放置在任何地方,然后编译它。您将在 n 将要运行的地方收到编译器错误。现在,您可以将这些宏替换为运行时宏BINARY_FLAG_RUNTIME(n)。这是唯一可行的方法。

【讨论】:

    【解决方案3】:

    我建议你使用两个宏。 BINARY_FLAG CONST_BINARY_FLAG 这将使您的代码更容易被其他人掌握。在撰写本文时,您确实知道它是否为 const。 而且我绝不会担心运行时开销。您的优化器,至少在 VS 中,为您解决。

    【讨论】:

      猜你喜欢
      • 2012-02-23
      • 1970-01-01
      • 1970-01-01
      • 2020-06-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多