Параметр шаблона указателя непигового типа

Я действительно не понимаю, почему приведенный ниже код не компилируется:

template<const char*>
struct Foo{};

constexpr const char s1[] = "test1";
constexpr const char* const s2 = "test2";

int main()
{
    Foo<s1> foo1; // ok
    // Foo<s2> foo2; // doesn't compile
}

Раскомментирование последней строки в main() делает g++ и clang++ испускать ошибки

error: 's2' is not a valid template argument because 's2' is a
variable, not the address of a variable

и

error: non-type template argument for template parameter of
      pointer type 'const char *' must have its address taken

соответственно.

Мои вопросы:

  • Почему s1 экземпляр ОК и s2 нет?
  • Есть ли нормальная ситуация, когда такой параметр шаблона указателя непигового типа используется?

Ответы

Ответ 1

Для 1.:

Из [temp.arg.nontype]

1 Аргумент шаблона для не-type template-parameter должен быть преобразованным константным выражением (5.20) типа шаблона-параметра. Для шаблона-шаблона непигового типа или типа указателя значение константного выражения не должно ссылаться на (или для типа указателя, не должно быть адресом):

[...]

(1.3) - строковый литерал (2.13.5),

s2 содержит адрес строкового литерала и поэтому не может использоваться в качестве параметра здесь. s1, с другой стороны, представляет собой массив char, который был инициализирован строковым литералом, но значение s1 (при преобразовании в const char*) не указывает на строковый литерал, используемый в инициализации.

Для 2.:

Возможно, указатели на функции? Тем не менее я не могу сказать, что когда-либо использовал указатель как параметр не-типа.

Ответ 2

В комментарии выше, vsoftco добавляет:

кажется чрезвычайно странным, строковые литераторы afaik не являются временными, но сохраняются в течение всей продолжительности программы, поэтому их адрес, безусловно, является константой времени компиляции (или, по крайней мере, тем, что я считаю)

Это правда. Однако стандарт не указывает, имеют ли строковые литералы уникальные адреса.

Некоторые линкеры объединяют или дедуплицируют строковые литералы. Я работал над системами, где "ello" == "hello"+1 фактически оценивает true. Другие линкеры настолько тупы, что "hello" в foo.cc имеет другой адрес из "hello" в bar.cc. Heck, некоторые крошечные компиляторы C настолько тупы, что "hello" может иметь два разных адреса в одной и той же единицы перевода!

Для такого немого компоновщика (или компилятора), должен ли Foo<"hello"> вызвать один экземпляр или два? Это...

const char *sa = "hello world";
const char *sb = "hello world";
assert(sa != sb);  // this assertion is permitted to succeed

template<char*> struct F {};
F<"hello world"> fa;
F<"hello world"> fb;
assert(!is_same<decltype(fa), decltype(fb)>::value);
    // should we permit this assertion to succeed also?

Комитет превосходно отказался открыть эту банку червей, просто запретив конструкцию.


Теперь, это возможно (для меня, на данный момент), что когда-то в будущем, Комитет мог бы поручить, чтобы все строковые литералы были дедуплицированы по тому же механизму, который в настоящее время используются для использования inline и template. То есть мы можем представить себе преобразование исходного уровня, которое превращает

const char *sc = "yoo hoo";

в

inline auto& __stringlit_yoo_x20hoo() {
    static const char x[] = "yoo hoo";
    return x;
}
const char *sc = __stringlit_yoo_x20hoo();

Тогда в любом месте программы будет только один экземпляр __stringlit_yoo_x20hoo (и только один экземпляр этой статической матрицы x), поэтому значение F<"yoo hoo"> будет однозначным. Реализация должна была бы называть именовать вещи недвусмысленно, но это простая проблема, как только вы уже посвятили себя созданию имен, таких как F<1+1> и F<FruitType,ORANGE> (которые компиляторы С++ делали навсегда).

... Но тогда у вас были бы еще проблемы с этими чрезвычайно умными компоновщиками (например, с теми, над которыми я работал), которые позволяют

assert("hello" == "hello\0world");  // this assertion is permitted to succeed

assert(!is_same_v< F<"hello">, F<"hello\0world"> >);
    // should we permit this assertion to succeed also?
    // Surely this way lies madness.

Ответ 3

Там последнее изменение в соответствующем стандартном тексте, но код не подходит ни в одной версии стандарта.

N4140 [temp.arg.nontype]/p1:

1 Аргумент шаблона для непигового, не-шаблона template-parameter должен быть одним из:

  • для нетипового шаблона-параметра интегрального или перечисляемого типа, преобразованное константное выражение (5.19) типа Шаблон-параметры; или
  • имя несимметричного шаблона; или
  • постоянное выражение (5.19), которое обозначает адрес полного объекта со статическим временем хранения и внешним или внутренним связь или функция с внешней или внутренней связью, включая шаблонов функций и шаблонов-шаблонов функций, но исключая нестатические члены класса, выраженные (игнорируя круглые скобки) как & id-expression, где id-выражение - это имя объекта или кроме того, что & может быть опущено, если имя относится к функции или массива и должны быть опущены, если соответствующие шаблон-параметр является ссылкой; или
  • константное выражение, которое вычисляет значение нулевого указателя (4.10); или
  • константное выражение, которое вычисляет значение указателя нулевого элемента (4.11); или
  • указатель на элемент, выраженный как описано в 5.3.1; или
  • постоянное выражение типа std::nullptr_t.

N4296 [temp.arg.nontype]/p1:

Аргумент шаблона для параметра шаблона, не относящегося к типу, должен быть преобразованное постоянное выражение (5.20) типа Шаблон-параметры. Для шаблона-шаблона типа non-type ссылочного или указательного типа значение константного выражения не должно ссылаться (или для типа указателя, не должен быть адресом):

  • подобъект (1.8),
  • временный объект (12.2),
  • строковый литерал (2.13.5),
  • результат выражения typeid (5.2.8) или
  • предопределенная переменная __func__ (8.4.1).

Версия N4140 является той, которая в настоящее время реализована компиляторами. Версия N4296 несколько более расслаблена, но ни в одном случае адрес строкового литерала не является допустимым аргументом шаблона.

Предположительно, причина в том, что аргументы шаблона должны искажаться, а искажение строкового литерала разумным способом, который работал бы на нескольких единицах перевода, было бы очень сложно, если не невозможно.