Подтвердить что ты не робот

Шаблонные трюки с константой char * как параметр непигового типа

Я очень хорошо знаю, что передача непосредственно const char* в качестве шаблона непигового параметра ошибочна, поскольку два идентичных строковых литерала, определенных в двух разных единицах перевода, могут иметь разные адреса (хотя большую часть времени компиляторы используют тот же адрес). Существует трюк, который можно использовать, см. Код ниже:

#include <iostream>

template<const char* msg>
void display()
{
    std::cout << msg << std::endl;
}

// need to have external linkage 
// so that there are no multiple definitions
extern const char str1[] = "Test 1"; // (1)

// Why constexpr is enough? Does it have external linkage?
constexpr char str2[] = "Test 2";    // (2)

// Why doesn't this work? 
extern const char* str3 = "Test 3";  // (3) doesn't work

// using C_PTR_CHAR = const char* const;   // (4) doesn't work either
extern constexpr C_PTR_CHAR str4 = "Test 4"; 

int main()
{
    display<str1>();    // (1')
    display<str2>();    // (2')
    // display<str3>(); // (3') doesn't compile 
    //display<str4>();  // (4') doesn't compile
}

В основном в (1) мы объявляем и определяем массив с внешней связью, который затем может использоваться как параметр шаблона в (1 '). Я это очень хорошо понимаю. Однако я не понимаю:

  • Почему работает constexpr версия (2)? У constexpr есть внешняя связь? Если нет, то определение одного и того же строкового литерала в другой единицы перевода может привести к дублированию экземпляра шаблона.

  • Почему (3) и (4) не работают? Для меня это кажется вполне разумным, но компилятор не верит в это:

    error: 'str3' не является допустимым аргументом шаблона, потому что 'str3' - это переменная, а не адрес переменной

4b9b3361

Ответ 1

1. Короткий ответ: он работает независимо от того, объявляется ли он constexpr, потому что вы определяете объект со статической продолжительностью хранения (это не строковый литерал - он хранит копию содержимое одного), а его адрес является постоянным выражением. Что касается связи, str2 имеет внутреннюю связь, но это прекрасно - его адрес может использоваться как аргумент шаблона непигового типа.

Длинный ответ:

В С++ 11 и 14, [14.3.2p1] говорит следующее:

Аргумент шаблона для непигового шаблона-шаблона без шаблона должен быть одним из:
[...]

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

[...]

Таким образом, вы можете использовать адрес объекта со статической продолжительностью хранения, но объект должен быть идентифицирован именем с привязкой (внутренним или внешним), а также тем, как вы выражаете этот адрес. (Строковые литералы не являются именами и не имеют привязки.)

Короче говоря, даже char str1[] = "Test 1"; работает. static char str1[] = "Test 1"; также прекрасен; GCC 5.1.0 отвергает его, но я думаю, что ошибка; Clang 3.6.0 принимает его.


О str2 linkage, С++ 11 и 14 [3.5p3] говорит:

Имя, имеющее область пространства имен (3.3.6), имеет внутреннюю связь, если это имя [...]

  • неопределенная переменная, которая явно объявлена ​​ const или constexpr, и не объявлено явно extern, а не ранее заявлено, что имеет внешнюю связь;

[...]

N4431 изменил это немного, в результате DR 1686, чтобы:

  • переменная типа энергонезависимой конструкции, которая не объявлена ​​явно extern и не объявлена ​​ранее связь;

отражающий тот факт, что constexpr подразумевает const-квалификацию для объектов.


2. Краткий ответ: для С++ 11 и 14 см. выше; для проекта С++ 1z, str3 не является постоянным выражением, поскольку сам указатель не является constexpr, а также адресом строкового литерала. str4 является постоянным, но все равно адресом строкового литерала.

Длинный ответ:

В текущем рабочем черновике N4431 ограничения на аргументы шаблона не-типа были ослаблены. [14.3.2p1] теперь говорит:

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

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

И это все ограничения. Часть преобразованного константного выражения довольно важна; полное определение длинное, но одна часть, относящаяся к нашему случаю, такова, что адрес объекта со статической продолжительностью хранения является таким выражением.

Также важно, что согласно [5.20p2.7] преобразование lvalue-to-rale, примененное к

нестабильное значение glvalue, которое относится к неопределенному объекту с constexpr, или это относится к не изменяемому под-объекту такого объект

также удовлетворяет условиям постоянного выражения. Это позволяет нам использовать некоторые переменные-указатели constexpr в качестве аргументов шаблона непигового типа. (Обратите внимание, что просто объявить переменную const недостаточно, так как она может быть инициализирована не константным выражением.)

Итак, что-то вроде constexpr const char* str3 = str1; отлично. Он принят Clang 3.6.0 в режиме С++ 1z (и отклонен в режиме С++ 14); GCC 5.1.0 все еще отклоняет его - похоже, что он еще не реализовал обновленные правила.


И все-таки, что случилось со строковыми литералами? Здесь проблема (N4431 [2.13.5p16]):

Оценка строкового литерала приводит к строковому литералу с статическая продолжительность хранения, инициализированная от заданных символов как указанных выше. Все ли струнные литералы различны (то есть, хранятся в объектах с неперекрывающимися объектами) и оценки строкового литерала дают тот же или другой объект не указано.

Реализации разрешено делать много вещей со строковыми литералами: смешивать, сопоставлять, перекрывать (полностью или частично), делать 7 копий из одной и той же единицы перевода - что угодно. Это делает адрес строкового литерала непригодным для использования в качестве аргумента шаблона непигового типа.