【问题标题】:Is a compiletime constant index into a compiletime constant array itself compiletime constant?编译时间常数索引是否是编译时间常数数组本身的编译时间常数?
【发布时间】:2009-04-20 05:21:59
【问题描述】:

我正在尝试玩花哨的游戏,它让 C++ 编译器在编译时合成常量字符串的哈希值。这可以让我用单个标识符替换字符串,从而大大节省代码大小和复杂性。

为了编程的清晰性和易用性,如果我可以在编译时检查和计算简单的内联字符串,例如“Hello”,它是指向编译时常量字符的编译时常量指针。

如果我可以在编译时索引这些,我可以制作一个模板元程序来做我想做的事。但尚不清楚 C++ 标准是否将 ct-constant 数组的 ct-constant 索引本身视为 ct-constant。

换个方式问,

 const char v="Hello"[0];

是非常有效的 C++(和 C)。但是值 v 是编译时间常数吗?

我已经相信答案是否定的,但实际上一些编译器在没有任何警告的情况下接受它,更不用说错误了。例如,以下代码编译并运行,甚至没有来自英特尔 C++ 编译器的警告:

#include <iostream>
const char value="Hello"[0];
template<char c>  void printMe()
{
  std::cout << "Template Val=" << c << std::endl;
}

int main()
{
  const char v='H';
  printMe<'H'>();
  printMe<v>();
  printMe<value>(); // The tricky and interesting case!
}

然而,微软的编译器根本不会编译,给出一个合理连贯的错误信息,关于使用带有内部链接的对象的模板。

我怀疑我的问题的答案是“不,即使对具有常量索引的常量数组的任何数组引用在编译时也是常量”。这是否意味着英特尔编译器的成功执行是英特尔编译器中的一个错误?

【问题讨论】:

    标签: c++ compiler-construction metaprogramming standards-compliance


    【解决方案1】:

    它也不适用于 GCC。

    然而,在语言兼容的观点之外,编译器优化器确实将其视为一个字符常量,这很好。我利用这一事实来允许预处理器生成字符常量(通过使用*#foo)。参见文件hdr.h 中的http://cvs.openbsd.org/cgi-bin/query-pr-wrapper?full=yes&numbers=1652。使用该宏,您可以编写

    DECR(h, e, l, l, o)
    

    而不是

    DECR('h', 'e', 'l', 'l', 'o')
    

    在我看来,更具可读性。 :-)

    【讨论】:

    • 聪明的宏 hack 使用 stringize 宏操作符来处理原始字符!当然,这在尝试使用模板参数时没有多大帮助。除非 varargs 样式的宏(C99??)可以剥离一个字符,构建一个新的 typedef 并将剩余的 var args 包含为递归类型 a-la Loki?
    【解决方案2】:

    好问题,是的,这是可以做到的,并且符合标准,它适用于 Microsoft、GCC 和 Intel,问题是你的语法错误:)

    一秒钟我会做一个样品... 好的,就到这里。这个示例是有效的 C++,我经常使用它,但实际上大多数程序员不知道如何正确使用它。

    template<char* MSG>
    class PrintMe
    {
    public:
        void operator()()
        {
            printf(MSG);
        }
    };
    
    char    MyMessage[6] = "Hello"; //you can't use a char*, but a char array is statically safe and determined at compiletime
    
    int main(int argc, char* argv[])
    {
        PrintMe<MyMessage> printer;
        printer();
        return 0;
    }
    

    【讨论】:

    • 这不是在编译时访问 DATA,只是访问 char 指针。例如,在打印机()之后;打电话可以加MyMessage[0]++;打印机();你会看到更新的数据。这意味着数据是在运行时而不是编译时访问的。但是您的示例仍然很有趣,因为可以将非常量指针作为模板参数传递.. 本身就令人惊讶!
    • 这意味着我不确定你想做什么,但如果我能做到以上,你也许能做你想做的事,你能更好地解释一下你最后一个模板是什么预期的行为是?
    • 好的,现在我想我明白了,但不,你做不到。因为它实际上取决于硬件的字节顺序以及其他因素
    • 嗯,我一直在想你的示例代码。令我惊讶的是,您可以将指向 char 数组的指针作为编译时模板参数传递!更令人惊讶的是,您不能将 const 指针传递给静态定义的内联字符串常量!我还注意到您必须在全局范围内定义字符数组,如果它是函数的本地,它将不起作用!这有点神秘,任何全局范围的对象指针都可以作为模板参数传递吗?很有趣!
    • 上面的代码是大量调查的结果,正如你提到的,它必须是全局的(但我通常将数组放在命名空间中,以保持干净)我使用这种技术来存储元数据在模板中很多。但是,据我所知,它仅适用于全局范围的 POD 指针。但是它可能只适用于某些编译器上的任何对象
    【解决方案3】:

    这里的相关区别是“积分常量表达式”和单纯的编译时常量之间的区别。 “3.0”是一个编译时常量。 “int(3.0)”也是一个编译时常量。但只有“3”是 ICE。 [见 5.19]

    More details at boost.org

    【讨论】:

    • 感谢您的参考,但它似乎并不适用。在示例代码中, const char value="Hello"[0];将 value 定义为一个整数常量表达式(如 Boost 页面所示的示例 3)。然而,该值作为模板参数被拒绝,因此整型常量表达式不是可在模板中使用的编译时常量。
    • 好点,但不相关。整数类型的全局常量是 ICE 当且仅当它是用 ICE 本身初始化的。例如, const char value = rand();也不是 ICE,但它看起来也像 Boost 中的示例 3。您的问题是一样的,“Hello”[0] 不是一个 ICE,因此您不能用它初始化其他 ICE。
    猜你喜欢
    • 2013-11-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-19
    • 2018-11-13
    • 2012-03-21
    • 2023-03-04
    • 1970-01-01
    相关资源
    最近更新 更多