Параметр шаблона указателя непигового типа
Я действительно не понимаю, почему приведенный ниже код не компилируется:
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 несколько более расслаблена, но ни в одном случае адрес строкового литерала не является допустимым аргументом шаблона.
Предположительно, причина в том, что аргументы шаблона должны искажаться, а искажение строкового литерала разумным способом, который работал бы на нескольких единицах перевода, было бы очень сложно, если не невозможно.