Почему переменные внешних связей используются в качестве постоянных выражений?

В обсуждении другого вопроса мне был приведен пример, где, по-видимому, связь идентификатора повлияла на его удобство использования в постоянном выражении:

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
}