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

时间:2016-10-24 21:07:50

标签: c++

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

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. 可用时,为什么内部链接会导致变量不能用作常量表达式?
  4. 有人建议符号在链接器上可见(或不可见)的问题与此相关。对我而言,即使添加foo<b>static变体仍然被允许,这似乎反驳了这一点 - 或者我错了吗?
  5. (我认为other questions充分涵盖foo<b>()foo<g>()之间的差异。

2 个答案:

答案 0 :(得分:6)

GCC错误。

N3337(这是C ++ 11 +编辑修正)[temp.arg.nontype] / 2有一个直接在点上的例子:

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示例的原因可能是它通过了不同的代码路径模式。

答案 1 :(得分:3)

这是一个奇怪的巧合。我昨晚刚刚在C++ Templates阅读了这篇文章。当使用指针作为模板非类型参数时,它是指针中包含的地址,而不是指针指向的值,该指针是作为模板参数替换的常量。因此,地址必须在编译时可知并且在所有编译单元中是唯一的,以避免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
}