Строковые литералы не допускаются как параметры не типового шаблона
Следующая цитата из C++ Templates от Addison Wesley. Может ли кто-нибудь, пожалуйста, помочь мне понять суть английского языка / непрофессионала его суть?
Поскольку строковые литералы являются объектами с внутренней связью (два строковых литерала с одинаковым значением, но в разных модулях являются разными объектами), их также нельзя использовать в качестве аргументов шаблона:
6 ответов
Ваш компилятор в конечном итоге работает с вещами, называемыми модулями перевода, неофициально называемыми исходными файлами. В этих единицах перевода вы идентифицируете различные сущности: объекты, функции и т. Д. Задача компоновщика состоит в том, чтобы соединить эти блоки вместе, и частью этого процесса является объединение идентификаторов.
Идентификаторы имеют связь †: внутренняя связь означает, что объект, названный в этой единице перевода, виден только этой единице перевода, в то время как внешняя связь означает, что объект виден другим единицам.
Когда объект отмечен static
Дается внутренняя связь. Итак, учитывая эти две единицы перевода:
// a.cpp
static void foo() { /* in a */ }
// b.cpp
static void foo() { /* in a */ }
Каждый из тех foo
относится к объекту (функция в данном случае), который виден только их соответствующим единицам перевода; то есть каждая единица перевода имеет свой foo
,
Вот тут-то и подвох: строковые литералы того же типа, что и static const char[..]
, То есть:
// str.cpp
#include <iostream>
// this code:
void bar()
{
std::cout << "abc" << std::endl;
}
// is conceptually equivalent to:
static const char[4] __literal0 = {'a', 'b', 'c', 0};
void bar()
{
std::cout << __literal0 << std::endl;
}
И, как вы можете видеть, значение литерала является внутренним для этой единицы перевода. Так что если вы используете "abc"
например, в нескольких единицах перевода все они оказываются разными сущностями. ‡
В целом это означает, что это концептуально бессмысленно:
template <const char* String>
struct baz {};
typedef baz<"abc"> incoherent;
Так как "abc"
отличается для каждой единицы перевода. Каждой единице перевода будет присвоен свой класс, потому что каждый "abc"
это другая сущность, даже если они предоставили "тот же" аргумент.
На уровне языка это навязывается тем, что не типовые параметры шаблона могут быть указателями на сущности с внешней связью; то есть вещи, которые ссылаются на одну и ту же сущность в единицах перевода.
Так что это хорошо
// good.hpp
extern const char* my_string;
// good.cpp
const char* my_string = "any string";
// anything.cpp
typedef baz<my_string> coherent; // okay; all instantiations use the same entity
† Не все идентификаторы имеют связь; некоторые из них не имеют, такие как параметры функции.
‡ Оптимизирующий компилятор будет хранить идентичные литералы по одному адресу для экономии места; но это качество реализации деталей, а не гарантия.
Это означает, что вы не можете сделать это...
#include <iostream>
template <const char* P>
void f() { std::cout << P << '\n'; }
int main()
{
f<"hello there">();
}
...так как "hello there"
не гарантируется 100% разрешение на одно целое значение, которое можно использовать для создания экземпляра шаблона один раз (хотя большинство хороших компоновщиков будут пытаться свести все случаи использования к связанным объектам и создать новый объект с одной копией строки).
Однако вы можете использовать внешние символьные массивы / указатели:
...
extern const char p[];
const char p[] = "hello";
...
f<p>();
...
Очевидно, строковые литералы, такие как "foobar", не похожи на другие встроенные типы литералов (например, int или float). У них должен быть адрес (const char*). Адрес действительно является константным значением, которое компилятор заменяет вместо того, где появляется литерал. Этот адрес указывает куда-то, зафиксированный во время компиляции, в памяти программы.
Это должно быть внутренней связи из-за этого. Внутренняя связь означает, что она не может быть связана между единицами перевода (скомпилированные файлы cpp). Компилятор может попытаться сделать это, но это не обязательно. Другими словами, внутренняя связь означает, что если вы взяли адрес двух одинаковых литеральных строк (т. Е. Значение const char *, в которое они переводятся) в разных файлах cpp, они, в общем, не были бы одинаковыми.
Вы не можете использовать их в качестве параметров шаблона, потому что они потребуют strcmp(), чтобы проверить, что они одинаковы. Если бы вы использовали ==, вы просто сравнили бы адреса, которые не были бы одинаковыми, когда шаблон создавался с одной и той же литеральной строкой в разных единицах перевода.
Другие более простые встроенные типы, такие как литералы, также являются внутренними связями (они не имеют идентификатора и не могут быть связаны друг с другом из разных единиц перевода). Однако их сравнение тривиально, так как это по значению. Таким образом, они могут быть использованы для шаблонов.
Как упоминалось в других ответах, строковый литерал нельзя использовать в качестве аргумента шаблона. Однако существует обходной путь, который имеет аналогичный эффект, но "строка" ограничена четырьмя символами. Это происходит из-за многосимвольных констант, которые, как обсуждалось в ссылке, вероятно, довольно непереносимы, но работали для моих целей отладки.
template<int32_t nFourCharName>
class NamedClass
{
std::string GetName(void) const
{
// Evil code to extract the four-character name:
const char cNamePart1 = static_cast<char>(static_cast<uint32_t>(nFourCharName >> 8*3) & 0xFF);
const char cNamePart2 = static_cast<char>(static_cast<uint32_t>(nFourCharName >> 8*2) & 0xFF);
const char cNamePart3 = static_cast<char>(static_cast<uint32_t>(nFourCharName >> 8*1) & 0xFF);
const char cNamePart4 = static_cast<char>(static_cast<uint32_t>(nFourCharName ) & 0xFF);
std::ostringstream ossName;
ossName << cNamePart1 << cNamePart2 << cNamePart3 << cNamePart4;
return ossName.str();
}
};
Может использоваться с:
NamedClass<'Greg'> greg;
NamedClass<'Fred'> fred;
std::cout << greg.GetName() << std::endl; // "Greg"
std::cout << fred.GetName() << std::endl; // "Fred"
Как я уже сказал, это обходной путь. Я не претендую на то, что это хороший, чистый, переносимый код, но другие могут найти его полезным. Другой обходной путь может включать несколько аргументов шаблона char, как в этом ответе.
Идея стандарта C++, допускающего только определенный тип параметров для шаблонов, заключается в том, что параметр должен быть постоянным и известным во время компиляции, чтобы генерировать код "специализированного класса".
Для этого конкретного случая: при создании строкового литерала их адрес неизвестен до времени связывания (связывание происходит после компиляции), поскольку два строковых литерала в разных единицах перевода - это два разных объекта (как блестяще объяснено в принятом ответе). Когда происходит компиляция, мы не знаем, какой адрес строкового литерала использовать для генерации специализированного кода класса из шаблонного класса.
В С++ 20 вы можете добиться большего успеха, используя встроенную внешнюю связь:
#include <cstdio>
inline const char myliteral[] = "Hello";
template <const char* Literal>
struct mystruct
{
auto print() {
printf(Literal);
}
};
int main(){
mystruct<myliteral> obj;
obj.print();
}
По сути, то, что он делает, оставляет компоновщику возможность выяснить, какое определение в конечном итоге используется.