【问题标题】:C++ Switch won't compile with externally defined variable used as caseC ++ Switch不会使用用作案例的外部定义变量进行编译
【发布时间】:2010-12-24 14:34:15
【问题描述】:

我正在使用 MinGW GNU 编译器编写 C++,当我尝试在 switch 语句中使用外部定义的整数变量作为 case 时,就会出现问题。我收到以下编译器错误:“case label does not reduce to an integer constant”。

因为我已经将整数变量定义为 extern 我相信它应该编译,有谁知道问题可能是什么?

下面是一个例子:

test.cpp

#include <iostream>
#include "x_def.h"

int main()
{
   std::cout << "Main Entered" << std::endl;


   switch(0)
   {
      case test_int:
         std::cout << "Case X" << std::endl;
         break;
      default:
         std::cout << "Case Default" << std::endl;
         break;
   }

   return 0;
}

x_def.h

extern const int test_int;

x_def.cpp

const int test_int = 0;

此代码将在 Visual C++ 2008 上正确编译。此外,我的一个蒙大拿州朋友检查了 ISO C++ 标准,似乎任何 const-integer 表达式都可以工作。这可能是编译器错误还是我遗漏了一些明显的东西?

这是我的编译器版本信息:

从 C:/MinGW/bin/../lib/gcc/mingw32/3.4.5/specs 读取规范
配置:../gcc-3.4.5-20060117-3/configure --with-gcc --with-gnu-ld --with-gnu-as --host=mingw32 --target=mingw32 --prefix= /mingw --enable-threads --disable-nls --enable-languages=c,c++,f77,ada,objc,java --disable-win32-registry --disable-shared --enable-sjlj-exceptions -- enable-libgcj --disable-java-awt --without-x --enable-java-gc=boehm --disable-libgcj-debug --enable-interpreter --enable-hash-synchronization --enable-libstdcxx-debug
线程型号:win32
gcc 版本 3.4.5 (mingw-vista special r3)

【问题讨论】:

  • 有趣:一个务实的和两个基于标准的答案,都来自具有> 5K代表的人,而基于标准的答案不同意。我认为该标准还不够清晰。
  • @David Thronley:这就是标准的乐趣:)。
  • 我一直在试图确定您的朋友来自蒙大拿州的相关性。蒙大拿人以善于解释标准而闻名吗?
  • 我认为要达到 C++ 标准,必须具备一定的铁石心肠和对枪支的熟悉。
  • 为了清楚起见,您能否也显示您用于编译代码的命令行?

标签: c++ gnu


【解决方案1】:

case 标签需要一个整数常量表达式,它有严格的要求,可以在编译时在使用时确定它们的值。

从 5.19 [expr.const] 开始,“整型常量表达式只能涉及字面量 (2.13)、枚举器、常量变量或整型或枚举类型的静态数据成员用常量初始化表达式 (8.5),..."。

在需要常量表达式的地方使用test_int 时,它是一个声明为externconst 变量,并且没有任何初始化程序并且不满足常量表达式的要求,尽管事实上实际上,您确实使用另一个翻译单元中的整数常量表达式对其进行了初始化。 (*这在标准的措辞中并不完全清楚,但这是我目前对它的解释。)

标准中的限制不允许以下用法:

void f(int a, int b)
{
    const int c = b;

    switch (a)
    {
    case c:
        //...
    }
}

在您的示例中,当编译器编译test.cpp 时,它无法确定x_def.cpp 中的初始化程序可能是什么。你可能已经这样做了:

const int test_int = (int)time();

显然,在这两个示例中,const int 的值都不能在编译时确定,这是整型常量表达式的意图。

【讨论】:

  • 我敢说这仍然是标准中的一个缺陷。您引用的文本没有提及“相同的翻译单元”。它说“常量变量”-检查-“用常量表达式初始化”-检查。
  • 我同意这不是很清楚,但我仍然会捍卫我的解释。如果我们忘记了另一个翻译单元,只看 test.cpp:extern const int test_int; test_int 是一个用常量表达式初始化的常量变量吗?我想说的是,执行人员此时有权拒绝并拒绝翻译。第 2.1 节说,在组合翻译单元之前对翻译单元进行语义分析,并且常量表达式似乎是一种语义限制,应该在翻译阶段的第 7 步中应用。
  • @Charles:你的例子有点不同。标准很清楚,不仅要求const 变量,而且const 变量必须用常量表达式初始化——而您的参数int b 显然不是常量表达式,所以c 显然不是一个常量表达式。
  • @Jerry Coffin:是的,我的示例应该显示其他不允许的内容,我不能 100% 确定最初的问题是否清楚 const int 和使用“const-integer expression”的整数常量表达式。我仍然认为没有初始化程序的extern const int 不满足要求,即使变量(在其他地方)使用合适的表达式初始化,但完全承认这是一种解释。
  • @Pavel Minaev:如果您还没有阅读我更新的答案,委员会已经同意这是一个缺陷,并投票决定在下一版本的标准。
【解决方案2】:

案例标签必须是编译时常量。这意味着编译器必须能够在编译时替换该值。尽管您的值是常量,但编译器至少要到链接时才能知道它们的值。

【讨论】:

    【解决方案3】:

    VC++ 是对的,g++ 是错的。案例标签必须是 integral constant expression(第 6.4.2/2 节),并且使用常量表达式初始化的整数类型的 const 变量是常量表达式(第 5.19/1 节)。

    编辑:主要是为了 Pavel,以及他对可能的 DR 的建议。 §5.19/2 已经完全重写了。 C++0x 添加了一个全新的constexpr 概念,扩展了被认为是常量表达式的东西。例如,在当前标准下,类似:

    int x() { return 10; }
    const int y = x();
    

    y 不是常量表达式。我们都可以很容易地看到它是(间接地)用文字 10 初始化的,但是编译器仍然不允许它作为常量表达式。在新标准下,可以将x() 指定为constexpr,而y 将是一个常量表达式。

    正如在 N2960 中制定的,§5.19/2 说表达式是常量表达式,除非它使用以下列表中的内容。然后它给出了一个页面长的列表,但是使用在当前编译单元中未初始化的const 变量似乎不是其中之一。 [编辑:见下文——阅读 CWG 第 721 期,我改变了主意。]

    就 VC++ 正确而 g++ 错误而言,我的意思只是在这个非常具体的方面。如果您正在谈论使标准的每个部分都正确,那么毫无疑问 both 都是“错误的”。我怀疑是否有人正在为任何一个实现export

    然而,

    export 确实指出了 C++ 似乎愿意将决策推迟到链接时间的程度。两阶段名称查找意味着在编译导出的模板时,除了不确定的常量表达式之外,还有很多其他内容。它甚至可能不知道一个特定的名称是指一个函数还是一个对象——但毫无疑问标准确实确实需要这个。手头的问题让我觉得基本上更容易处理。

    编辑:我进行了一些搜索,发现核心工作组问题 721。Jame 的问题与手头的问题非常相似(“但是,这并不要求初始化发生在同一翻译单元并在常量表达式之前...")。提议的决议增加了以下短语:“......带有前面的初始化......”。至少在我阅读时,这意味着委员会同意在当前标准下,代码必须被接受,但在新标准下是不允许的。

    该措辞已在今年 7 月达成一致,但(还没有?)出现在 N2960 中,我认为这是 C++0x 的最新草案。

    【讨论】:

    • 我用 5.19/1 表示用常量表达式在这个翻译单元中初始化。好的,在其他尚未编写的翻译单元中,它可能已用常量表达式初始化。或使用非常量表达式。编译器无法知道。老实说,我不认为标准的意图是将 switch 语句的生成推迟到链接时间(以及所有其他使用整数常量表达式的代码,例如数组大小和位字段的大小)。
    • 或者换个角度看:在编译test.cpp的上下文中,test_int不是用常量表达式初始化的。它根本没有初始化,它只是声明为 extern。我不认为sizeof 可以工作,如果在加载包含extern const int 初始化的某些dll 之前它无法知道结构的数组成员的大小。
    • @Steve,标准文本中没有任何内容支持这种解释。我也认为这可能是预期的,但这不是它目前所说的。也许是 DR 的时候了。
    • @Steve:虽然你的立场是有道理的,但我在标准中找不到任何支持它的东西。
    • 嗯,我认为它模棱两可的原因是英文中看起来丢失的分词“initialized”可能表示“已初始化”,或者可能表示“已初始化”。预期的意思是前者,我不认为该措辞禁止预期的阅读。但是需要进行更正,因为措辞无法阻止后一种阅读(即使,正如查尔斯在其他地方所说,后一种阅读与定义的编译阶段相矛盾)。当您发明一种过于复杂而无法正式定义的语言时,就会发生这种情况......
    【解决方案4】:

    我无法在使用 VC++2008 的简单示例中重现这一点:

    test.cpp:

    extern const int n;
    int main() {
        switch (0) {
        case n: break;
        }
    }
    

    test2.cpp:

    extern const int n = 123;
    

    编译:

    cl.exe test.cpp test2.cpp
    

    输出:

    test.cpp(4) : error C2051: case expression not constant

    【讨论】:

    • 模块必须单独编译(而不是链接)时间可见。尝试像 OP 一样在 test.cpp 中包含 test2.cpp,它应该可以工作。
    • 在他的示例中,我没有看到一个 .cpp 的 #include 到另一个。他 #include 两个 .cpp 中的标头,但标头只有 extern const 声明,而不是带有初始化程序的定义。
    【解决方案5】:

    MS 编译器在这里有点调皮。当您在同一编译单元中使用该常量编译常量初始化和 case 语句时,它会在编译时计算出常量值。

    一旦你尝试使用初始化它的编译单元的extern const outside(即包含初始化的 cpp 文件或它包含的任何文件),编译器将与同样的错误。 Fred Larson 是正确的,编译器不应该直到链接时间才知道常量值,因此它不能被接受为 switch 常量,它只是 MS 编译器有点作弊。

    您的问题的解决方案是使用宏,您有什么理由不想#define 常量?

    【讨论】:

    • 您甚至不必#define 它。带有内部链接的const int 使用常量表达式初始化就可以了。这可以在标题中完成。
    • 确实有效。几乎就像它应该是枚举的一部分,因为它在 switch 语句中使用。不知道程序的目的很难说。
    • 问题中的demo代码中的初始化和case语句在哪里看到的?
    • 在这种情况下,OP 代码不会在 MSVC 2008 中使用默认设置进行编译。我得到error C2051: case expression not constant
    【解决方案6】:

    这是一个更简单的测试:

    test_int.cpp:

    const int test_int = 10;
    

    ma​​in.cpp:

    #include <iostream>
    using std::cout;
    using std::endl;
    
    extern const int test_int;
    
    int main() {
        cout << test_int << endl;
        return 0;
    }
    

    在 G++ 中,我得到一个未定义的引用。但是,在 C 中做同样的事情是可行的。根据 http://gcc.gnu.org/ml/gcc/2005-06/msg00325.html ,一个 const 变量在 C++ 中隐式地具有内部链接。在 C 中似乎不是这种情况。

    【讨论】:

    • 如果在定义之前将extern const声明为extern const,您仍然可以在C++中使用外部链接创建const变量。
    【解决方案7】:

    我用的是“gcc (SUSE Linux) 4.3.2”,效果差不多,还是有点陌生​​。

    我的定义是:

    namespace operations{
       const cOpDummy OpDummy();
       const cInitOperator InitOperator();
    };
    
    const unsigned long ulNumberOfOperations = 2;
    
    const cOperation * arrayOperations[] = {
       & (operations::OpDummy),
       & (operations::InitOperator)
    };
    

    另一个文件中的外部声明是:

    extern const unsigned long ulNumberOfOperations;
    extern const cOperation * arrayOperations[];
    

    有趣的是:编译器只为“ulNumberOfOperations”提供“对 ulNumberOfOperations 的未定义引用”,但可以使用“arrayOperations[]”。 我的解决方法是声明“ulNumberOfOperations”不是常量。

    【讨论】:

      【解决方案8】:

      从 c++11 开始,您可以构建一个小模板框架来为您提供如下语法:

      void test(int a, int x, int y, int z)
      {
          std::cout << "given " << a << ", choosing ";
          given(a)
              .when(x, [] { std::cout << "x\n"; })
              .when(y, [] { std::cout << "y\n"; })
              .when(z, [] { std::cout << "z\n"; })
              .when(any_other, [] { std::cout << "none of the above\n"; });
      }
      

      完整演示:

      #include <iostream>
      
      
      struct any_object {};
      constexpr auto any_other = any_object {};
      
      template<class Expr>
      struct when_object
      {
      
          template<class T, class F>
          constexpr when_object& when(T const& value, F&& f)
          {
              if (not executed and expr == value) {
                  executed = true;
                  f();
              }
              return *this;
          }
      
          template<class F>
          constexpr void when(any_object, F&& f)
          {
              if (not executed) {
                  executed = true;
                  f();
              }
          }
      
          Expr const& expr;
          bool executed = false;
      };
      
      template<class Expr>
      constexpr auto given(Expr const& expr)
      {
          return when_object<Expr> {expr};
      }
      
      void test(int a, int x, int y, int z)
      {
          std::cout << "given " << a << ", choosing ";
          given(a)
              .when(x, [] { std::cout << "x\n"; })
              .when(y, [] { std::cout << "y\n"; })
              .when(z, [] { std::cout << "z\n"; })
              .when(any_other, [] { std::cout << "none of the above\n"; });
      }
      
      
      int main()
      {
          test(4, 4, 5, 6);
          test(4, 3, 4, 5);
          test(4, 2, 3, 4);
          test(1, 2, 3, 4);
      }
      

      预期结果:

      given 4, choosing x
      given 4, choosing y
      given 4, choosing z
      given 1, choosing none of the above
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-02-14
        • 2023-03-16
        • 1970-01-01
        • 1970-01-01
        • 2011-10-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多