Я придумала решение проблемы, но я не уверена, будет ли она работать всегда или только на моем компиляторе. Во-первых, проблема: я заметил, что в ряде ситуаций желательно иметь шаблонный класс, который обновляется каждый раз, когда он используется, даже если ему присваиваются одинаковые типы (скажем, у вашего шаблонного класса есть статические члены, которые инициализируются для вызовов функций у этого есть некоторый важный побочный эффект - и вы хотите, чтобы этот побочный эффект был сделан каждый раз, когда шаблон используется). Самый простой способ сделать это - дать вашему шаблону дополнительный целочисленный параметр:
template<class T, class U, int uniqueify>
class foo
{
...
}
Но теперь вам нужно вручную убедиться, что каждый раз, когда вы используете foo, вы передаете ему другое значение для uniqueify. Наивное решение состоит в том, чтобы использовать __LINE__
так:
#define MY_MACRO_IMPL(line) foo<line>
#define MY_MACRO MY_MACRO_IMPL(__LINE__)
Это решение имеет проблему, хотя - __LINE__
получает сброс для каждой единицы перевода. Таким образом, если две единицы перевода используют шаблон в одной строке, шаблон создается только один раз. Это может показаться маловероятным, но представьте себе, насколько сложно отладить ошибку компилятора, если бы она произошла. Точно так же вы можете попробовать как-то использовать __DATE__
в качестве параметра, но с точностью до секунды, и это время начала компиляции, а не когда она достигает этой строки, поэтому, если вы используете параллельную версию make, довольно вероятно иметь две единицы перевода с одинаковыми __DATE__
.
Другое решение состоит в том, что некоторые компиляторы имеют специальный нестандартный макрос __COUNTER__
, который начинается с 0 и увеличивается каждый раз, когда вы его используете. Но он страдает от той же проблемы - он сбрасывается при каждом вызове препроцессора, поэтому он сбрасывается при каждом переводе.
Еще одно решение, это использовать __FILE__
и __LINE__
вместе:
#define MY_MACRO_IMPL(file, line) foo<T, U, file, line>
#define MY_MACRO MY_MACRO_IMPL(T, U, __FILE__, __LINE__)
Но вы не можете передавать символьные литералы в качестве параметров шаблона в соответствии со стандартом, поскольку они не имеют внешней связи.
Даже если это сработало, содержит ли __FILE__
абсолютный путь к файлу или просто имя самого файла не определено в стандарте, поэтому, если у вас есть два одинаковых именованных файла в разных папках, это может все еще сломаться Итак, вот мое решение:
#ifndef toast_unique_id_hpp_INCLUDED
#define toast_unique_id_hpp_INCLUDED
namespace {
namespace toast {
namespace detail {
template<int i>
struct translation_unit_unique {
static int globally_unique_var;
};
template<int i>
int translation_unit_unique<i>::globally_unique_var;
}
}
}
#define TOAST_UNIQUE_ID_IMPL(line) &toast::detail::translation_unit_unique<line>::globally_unique_var
#define TOAST_UNIQUE_ID TOAST_UNIQUE_ID_IMPL(__LINE__)
#endif
Почему это работает не очень понятно без примера использования, но сначала краткий обзор. Главное, что я понял, было то, что каждый раз, когда вы создаете глобальную переменную или статическую переменную-член, вы создаете уникальный для всей программы номер в форме адреса этой переменной. Так что это дает нам уникальный номер, который доступен во время компиляции. __LINE__
гарантирует, что мы не получим столкновения внутри одной и той же единицы перевода, а внешнее анонимное пространство имен гарантирует, что переменные являются разными экземплярами (и, следовательно, получают разные адреса) в разных единицах перевода.
Пример использования:
template<int* unique_id>
struct special_var
{
static int value;
}
template<int* unique_id>
int special_var<unique_id>::value = someSideEffect();
#define MY_MACRO_IMPL(unique_id) special_var<unique_id>
#define MY_MACRO MY_MACRO_IMPL(TOAST_UNIQUE_ID)
И foo.cpp становится:
#include <toast/unique_id.hpp>
...
typedef MY_MACRO unique_var;
typedef MY_MACRO unique_var2;
unique_var::value = 3;
unique_var2::value = 4;
std::cout << unique_var::value << unique_var2::value;
Несмотря на то, что шаблон является одним и тем же, а пользователь не предоставляет никаких дифференцирующих параметров, unique_var
и unique_var2
различаются.
Меня больше всего беспокоит, что адрес переменной в анонимном пространстве имен будет доступен во время компиляции. Технически, анонимное пространство имен похоже на объявление внутренней связи, а параметры шаблона не могут иметь внутренней связи. Но то, как в стандарте говорится об обработке анонимных пространств имен, точно так же, как переменная была объявлена как часть пространства имен с уникальным именем для всей программы, что означает, что технически она имеет внешнюю связь, хотя мы и не обычно не думаю об этом как таковой. Поэтому я думаю, что стандарт на моей стороне, но я не уверен.
Я не знаю, справился ли я лучше всего с объяснением , почему это было бы полезно, но ради этого обсуждения, клянусь;)