Данная проблема заинтересовала меня благодаря одному из вопросов на сайте ru.stackoverflow.com. При написании статьи я руководствовался стандартом C++11 и использовал компилятор GCC 6.2.0.
Рассмотрим возможность применения строковых литералов (string literals) в качестве аргументов шаблона при инстанцировании.
Например:
#include <iostream>
template<const char* s>
class A {
public:
void foo() {
std::cout << s << "\n";
}
};
int main() {
A<"Hello, world!"> a;
a.foo();
return 0;
}
Данный код не будет скомпилирован с сообщением об ошибке:
main.cpp: In function ‘int main()’:
main.cpp:12:22: error: ‘"Hello, world!"’ is not a valid template argument for type ‘const char*’ because string literals can never be used in this context
A<"Hello, world!"> a;
Давайте попробуем разобраться, почему строковый литерал никогда не может быть использован в данном контексте и какие есть варианты решения этой проблемы.
Начнём с рассмотрения самого шаблона:
template<const char* s> class A {...}
Это определение шаблона класса с использованием бестипового параметра (non-type template parameter). При инстанцировании шаблона на бестиповый аргумент действует ряд ограничений:
&Class::Member
or a constant expression that evaluates to null pointer or std::nullptr_t value. Обратите внимание на второй пункт: для указателей на объекты аргумент шаблона должен указывать на адрес объекта со статической продолжительностью хранения и линковкой (либо внешней, либо внутренней), или быть константным выражением, результатом которого является соответствующий null
указатель или std::nullptr_t
значение. Обратимся к документации на строковые литералы, где есть следующие записи:
String literals have static storage duration, and thus exist in memory for the life of the program.
...
The compiler is allowed, but not required, to combine storage for equal or overlapping string literals. That means that identical string literals may or may not compare equal when compared by pointer.
Как видим, строковые литералы имеют статическую продолжительность хранения, но не имеют линковки. Это означает, что в пределах даже одной единицы трансляции адреса одинаковых строковых литералов могут различаться (а могут и совпадать, всё зависит от конкретной реализации компилятора), и использование в различных участках кода записи вида A<"Hello, world!">
может приводить к инстанцированию разных шаблонов. Поэтому этот случай оговорен в стандарте и явно запрещён:
In particular, this implies that string literals, addresses of array elements, and addresses of non-static members cannot be used as template arguments to instantiate templates whose corresponding non-type template parameters are pointers to objects.
Разобравшись с причиной ошибки, попробуем её устранить:
#include <iostream>
template<const char* s>
class A {
public:
void foo() {
if (s) {
std::cout << s << "\n";
} else {
std::cout << "Hello from nullptr!" << "\n";
}
}
};
// Создаем дополнительную переменную со статической
// продолжительностью хранения и внешней линковкой.
extern const char p[];
const char p[] = "Hello from extern!";
// Либо используем переменную времени компиляции
// с внутренней линковкой.
constexpr static const char s[] = "Hello from constexpr!";
// Либо используем константное выражение,
// результатом которого является nullptr.
constexpr static const char* c = nullptr;
int main() {
A<p> a;
a.foo();
A<s> b;
b.foo();
A<c> c;
c.foo();
return 0;
}
В консоль будет выведено:
Hello from extern!
Hello from constexpr!
Hello from nullptr!