Ваш компилятор в конечном итоге работает с вещами, называемыми единицами перевода , неофициально именуемыми исходными файлами . В этих единицах перевода вы идентифицируете различные сущности: объекты, функции и т. Д. Задача компоновщика состоит в том, чтобы соединить эти блоки вместе, и частью этого процесса является объединение идентификаторов.
Идентификаторы имеют связь † : внутренняя связь означает, что объект, названный в этой единице перевода, виден только этой единице перевода, тогда как внешний связь означает, что объект виден другим единицам.
Когда объект помечен 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
† Не все идентификаторы имеют связь; у некоторых их нет, например, параметры функции.
‡ Оптимизирующий компилятор будет хранить идентичные литералы по одному адресу для экономии места; но это качество деталей реализации, а не гарантия.