Почему переменные внешних связей используются в качестве постоянных выражений?
В обсуждении другого вопроса мне был приведен пример, где, по-видимому, связь идентификатора повлияла на его удобство использования в постоянном выражении:
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>
. Однако я начал сомневаться в этой позиции.
- Это действительно связка, или это какой-то другой атрибут, предоставленный
extern
, который позволяет foo<a>()
?
- В чем обоснование разрешения
foo<a>()
, но не foo<g>()
? В частности, если это связано с привязкой, почему внутренняя связь приводит к тому, что переменная не может использоваться в качестве постоянного выражения, когда можно использовать одну и ту же переменную, объявленную как extern
?
- Было высказано мнение о том, что вопрос о том, чтобы символ был видимым (или нет) компоновщику, имеет значение здесь. Мне кажется, что вариант
foo<b>
по-прежнему разрешен даже при добавлении static
, это опровергает - или я ошибаюсь?
- (Разница между
foo<b>()
и foo<g>()
адекватно покрывается другими вопросами, я думаю).
Ответы
Ответ 1
Ошибка GCC.
N3337 (который содержит исправления для С++ 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
В С++ 03 аргументы шаблона-указателя/указателя ограничены вещами с внешней связью, но это ограничение было удалено в С++ 11.
Правила для аргументов шаблона reference/pointer ослаблены в С++ 17, чтобы разрешить все константные выражения, поэтому, вероятно, причиной того, почему GCC принимает пример с -std=c++1z
, является то, что он проходит через другой путь кода в этом режиме.
Ответ 2
Это странное совпадение. Я просто читал об этом в С++ Templates только вчера вечером. При использовании указателя в качестве параметра непигового шаблона это адрес, содержащийся в указателе, а не значение, на которое указывает указатель, который является константой, заменяемой как аргумент шаблона. Таким образом, адрес должен быть узнаваемым во время компиляции и уникален во всех единицах компиляции, чтобы избежать нарушений ODR. Это относится к переменным constexpr
и extern
, но не к файлам или глобальной привязке. Вот пример.
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
}