Некоторые const char * недоступны во время компиляции?

Предположим, у нас есть шаблонная функция с параметром не типа const char *, например:

template <const char * MESSAGE> void print() {
    std::cout << MESSAGE << '\n';
}

Использование этого шаблона не будет проблемой, поскольку журнал MESSAGE может быть выведен во время компиляции, поэтому следующие варианты использования допустимы:

namespace {
    char namespace_message[] = "Anonymous Namespace Message";
    constexpr char namespace_constexpr_message[] = "Anonymous Namespace Constexpr Message";
}

char message[] = "Message";
constexpr char constexpr_message[] = "Constexpr Message";

int main()
{
    print<namespace_message>();
    print<namespace_constexpr_message>();

    print<message>();
    print<constexpr_message>();

    return 0;
}

Но приведенные ниже не являются (см. Здесь):

namespace {
const char namespace_const_message[] = "Anonymous Namespace Const Message";
}

const char const_message[] = "Const Message";

int main()
{
    print<namespace_const_message>();
    print<const_message>();
    print<"Literal">();

    return 0;
}

Ошибки, сгенерированные приведенным выше кодом, следующие:

значение '{anonymous} :: namespace_const_message' не может использоваться в постоянном выражении

Я не понимаю, почему namespace_const_message нельзя использовать в постоянном выражении, а namespace_message -; если я должен сделать ставку на то, что одно из них нельзя будет использовать в постоянном выражении, я сделаю ставку на отсутствие констант, но это тот, который уже работает как постоянное выражение!

примечание: '{anonymous} :: namespace_const_message' не был объявлен 'constexpr'

namespace_message не был объявлен как constexpr и используется в константном выражении, и его значение выводится во время компиляции. Зачем constexpr, если выражение const и не требуется, если no-const?

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

Наконец, последняя ошибка:

"Literal" "не является допустимым аргументом шаблона для типа" const char * ", потому что строковые литералы никогда не могут использоваться в этом контексте.

Итак, на удивление (по крайней мере, для меня это было неожиданностью) строковый литерал не может использоваться в качестве аргумента шаблона, но пока строка (ну, указатель на массив символов с завершающим нулем) является значением времени компиляции, она могут использоваться как параметры шаблона, не относящиеся к типу, поэтому: они доступны во время компиляции, если «они являются lvalue» (но они уже lvalues ​​!).

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

Итак, в чем вопрос?

  • Почему namespace_const_message и const_message недоступны во время компиляции и поэтому запрещены в функции шаблона print?
  • Верно ли мое предположение о строковых литералах?

Спасибо.


person PaperBirdMaster    schedule 02.03.2015    source источник


Ответы (2)


Переменная создания экземпляра шаблона должна была иметь внешнюю связь, а const был неявно внутренней связью. Итак, вы должны написать:

extern char const constMessage[] = "Const message";

(Другой альтернативой может быть статический член класса. Статические члены класса всегда имеют внешнюю связь.)

Случай строковых литералов в чем-то похож: их тип char const[]. Но это еще хуже: экземпляры шаблонов (по крайней мере, ранние) нуждаются в имени, а строковый литерал его не имеет. Более того, не указано, являются ли идентичные строковые литералы одним и тем же объектом или нет, поэтому в следующем:

template <char const* m>
struct Toto { char const* f() const; };

Toto <"titi"> t1;
Toto <"titi"> t2;

не уточняется, имеют ли t1 и t2 один и тот же тип или нет.

person James Kanze    schedule 02.03.2015
comment
Отличный ответ по поводу связи! хороший. Я хотел бы попросить вас предоставить дополнительную информацию о проблеме связи; AFAIK безымянное пространство имен вызывает внутреннюю связь, и вы говорите, что переменная создания экземпляра шаблона должна иметь внешнюю связь, так почему namespace_message и namespace_constexpr_message принимаются в шаблоне? Не могли бы вы (пожалуйста) расширить свой ответ? - person PaperBirdMaster; 02.03.2015
comment
@PaperBirdMaster: Это не относится к безымянным пространствам имен. Это поясняется в сноске 94 C ++ 11: Хотя сущности в безымянном пространстве имен могут иметь внешнюю связь, они эффективно квалифицируются именем, уникальным для их единицы перевода, и поэтому не могут быть видны ни в одной другой единице перевода. Правила приведены в 3.5 / 3 и 3.5 / 4. - person Lightness Races in Orbit; 02.03.2015
comment
@LightnessRacesinOrbit Итак, я ошибся насчет безымянных пространств имен: они НЕ ПРИНУДИТЕЛЬНО внутреннюю связь, они МОГУТ иметь внутреннюю связь. Спасибо за разъяснения. - person PaperBirdMaster; 03.03.2015
comment
Члены статического класса всегда имеют внешнюю связь - хотя этот оператор присутствовал в стандартной формулировке C ++ 11, теперь он считается неправильным согласно DR 1603; в черновиках после C ++ 14 было уточнено, что эти имена связаны с именем их класса. - person bogdan; 03.06.2015
comment
@LightnessRacesinOrbit Эта сноска считается неправильной согласно DR 1603 и был удален в черновиках после C ++ 14. В примечании к DR четко говорится: Имена в безымянных пространствах имен никогда не имеют внешней связи. (В текущем черновике все еще есть небольшое несоответствие, связанное с этим, но я не буду вдаваться в это здесь). что, насколько я понимаю, DR считает сноску некорректной даже в контексте C ++ 11. - person bogdan; 03.06.2015
comment
@bogdan Это было правильно в C ++ 98 и C ++ 03, поскольку сами классы всегда имели внешнюю связь (кроме локальных классов, но локальный класс не мог иметь статический член данных). - person James Kanze; 08.06.2015
comment
@JamesKanze Да, но это не делает утверждение правильным в 2015 году, когда оно было написано (вопрос помечен как C++, без версии). Я думаю, что было бы неплохо уточнить некоторые из содержащихся там утверждений (о связях и именах) последней версией стандарта, в которой они действительны, иначе они могут сбить людей с пути как сейчас, так и в будущем. - person bogdan; 08.06.2015

Из стандарта С ++ 11 §14.3.2.1

Шаблонные аргументы, не относящиеся к типу

Аргумент шаблона для параметра шаблона, не являющегося типом и шаблоном, должен быть одним из следующих:

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

На ваши вопросы:

Почему namespace_const_message и const_message недоступны во время компиляции и, следовательно, запрещены в функции шаблона печати?

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

Верно ли мое предположение о строковых литералах?

Об этом есть примечание сразу после аргументов:

Примечание: строковый литерал (2.14.5) не удовлетворяет требованиям ни одной из этих категорий и, следовательно, не является приемлемым аргументом-шаблоном.

person hlscalon    schedule 02.03.2015
comment
Раздражает то, что не приводится причина для запрета строковых литералов, а указывается +1 для цитирования стандарт C ++ 11 - person Qwertie; 02.02.2019