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

Строковые литералы не разрешены как параметры шаблона нестандартного типа

Следующая цитата из С++ Templates от Addison Wesley. Может кто-то, пожалуйста, помогите мне понять на простом английском языке/непрофессионалы его смысл?

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

4b9b3361

Ответ 1

В конечном итоге ваш компилятор работает с вещами, называемыми единицами перевода, неофициально называемыми исходными файлами. В этих единицах перевода вы идентифицируете разные объекты: объекты, функции и т.д. Задача компоновщика состоит в том, чтобы объединить эти единицы вместе, и часть этого процесса объединяет идентификаторы.

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

Когда объект помечен 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

† Не все идентификаторы имеют связь; некоторые из них не имеют таких параметров, как параметры функции.

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

Ответ 2

Это означает, что вы не можете этого сделать...

#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>();
...

Ответ 3

Очевидно, строковые литералы, подобные "foobar", не похожи на другие литеральные встроенные типы (например, int или float). Они должны иметь адрес (const char *). Адрес - это действительно постоянное значение, которое компилятор заменяет вместо того, где появляется литерал. Этот адрес указывает где-то, зафиксированный во время компиляции, в памяти программы.

Из-за этого должна быть внутренняя связь. Внутренняя связь просто означает, что не может быть связана между единицами перевода (скомпилированные файлы cpp). Компилятор может попытаться сделать это, но не требуется. Другими словами, внутренняя связь означает, что если вы взяли адрес двух идентичных строк литерала (т.е. Значение const char *, которое они перевели) в разных файлах cpp, они не будут одинаковыми вообще.

Вы не можете использовать их в качестве параметров шаблона, потому что им потребуется strcmp(), чтобы проверить, что они одинаковые. Если вы использовали ==, вы просто сравнивали бы адреса, которые не совпадали бы при создании экземпляра шаблона с той же буквенной строкой в ​​разных единицах перевода.

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

Ответ 4

Как упоминалось в других ответах, строковый литерал не может использоваться в качестве аргумента шаблона. Существует, однако, обходной путь, который имеет аналогичный эффект, но "строка" ограничена четырьмя символами. Это связано с многосимвольными константами, которые, как обсуждалось в ссылке, вероятно, скорее не переносятся, но работают для моих целей отладки.

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, как в этом ответе.

Ответ 5

Идея стандарта С++, позволяющая определенным типам параметров шаблонам, заключается в том, что параметр должен быть постоянным и известен во время компиляции, чтобы генерировать код "специализированного класса".

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