Строковые литералы не допускаются в качестве параметров шаблона не тип - PullRequest
36 голосов
/ 05 апреля 2011

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

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

Ответы [ 5 ]

47 голосов
/ 05 апреля 2011

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

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

Когда объект помечен 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" является отдельной сущностью, даже если они предоставили один и тот же аргумент.

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

Так что все в порядке:

// 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

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

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

11 голосов
/ 05 апреля 2011

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

#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>();
...
7 голосов
/ 05 апреля 2011

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

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

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

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

3 голосов
/ 14 марта 2013

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

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

0 голосов
/ 15 июня 2015

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

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

...