为什么外部链接变量可用作常量表达式

Why are external-linkage variables usable as constant expressions?

本文关键字:常量 表达式 外部 链接 变量 为什么      更新时间:2023-10-16

在另一个问题的讨论中,我得到了一个例子,显然标识符的链接影响了它在常量表达式中的可用性:

extern char const a[] = "Alpha";
char constexpr b[] = "Beta";
char const g[] = "Gamma";
template <const char *> void foo() {}
auto main()
    -> int
{
    foo<a>();     // Compiles
    foo<b>();     // Compiles
    foo<g>();     // Doesn't compile
}

最后一个错误(使用GCC)是:

test.cc: In function 'int main()':
test.cc:12:13: error: the value of 'g' is not usable in a constant expression
         foo<g>();     // Doesn't compile
             ^
test.cc:3:16: note: 'g' was not declared 'constexpr'
     char const g[] = "Gamma";
                ^

我可能在前面的讨论中错过了这个例子的意义,因为我认为区分foo<a>foo<g>的不可能仅仅是连锁——然而,我开始怀疑这种立场。

  1. 是真的链接,还是extern授予的其他属性,允许foo<a>() ?
  2. 允许foo<a>()而不允许foo<g>()的基本原理是什么?特别是,如果它是由链接决定的,当声明为extern 的相同变量可用时,为什么内部链接会导致变量不能用作常量表达式呢?
  3. 有人建议,符号对链接器可见(或不可见)的问题与此相关。对我来说,事实似乎foo<b>变体仍然被允许,即使添加static反驳了这一点-或者我错了吗?
  4. (foo<b>()foo<g>()之间的差异已被其他问题充分涵盖,我认为)。

GCC bug。

N3337(这是c++ 11 +的编辑修复)[temp.arg.]Nontype]/2有一个直接在point上的例子:

template<class T, const char* p> class X {
    /* ... */
};
X<int, "Studebaker"> x1; // error: string literal as template-argument
const char p[] = "Vivisectionist";
X<int,p> x2; // OK

在c++ 03中,引用/指针模板参数仅限于具有外部链接的东西,但在c++ 11中删除了该限制。

引用/指针模板参数的规则在c++ 17中放宽了,允许所有常量表达式,所以GCC接受-std=c++1z示例的原因可能是它在该模式下经历了不同的代码路径。

这是一个奇怪的巧合。我昨晚刚刚在 c++模板中读到这一点。当使用指针作为模板非类型形参时,它是指针中包含的地址,而不是指针所指向的作为模板实参的常量的值。因此,地址必须在编译时是可知的,并且在所有编译单元中是唯一的,以避免违反ODR。这对于constexprextern变量是正确的,但对于具有文件或全局链接的变量则不是。下面是一个例子。

static char const foo[] = "Hello";
char const bar[] = "Hello";
constexpr char const baz[] = "Hello";
extern char const qux[] = "Hello";
template <char const*>
struct my_struct{};
int main() {
    my_struct<foo> f;       // ERROR: Address is unique, but not known until runtime
    my_struct<bar> b;       // ERROR: Address may or may not be unique (ODR violation) and not known until runtime
    my_struct<baz> bz;      // OK: constexpr
    my_struct<qux> q;       // OK: extern
}