Есть ли шаблон, который может генерировать статические/динамически связанные версии класса?

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

Для быстрого примера предположим, что у меня есть шаблон структуры A:

template<bool dynamic, int value=0> struct A
{
    static const int Value = value;
};


template<> struct A<true>
{
    int Value;
    A(int value) : Value(value) {}
};

Эти определения позволяют пользователям библиотеки создавать экземпляр A статически и динамически:

A<true> dynamicA = A<true>(5);

A<false, 5> staticA;

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

// It would be much harder to generate a static version of this class, 
// though it is possible with type lists.  Also, the way I'm imagining it, 
// the resulting classes probably wouldn't be very easy to use.
struct A
{
   vector<int> Values;
    A(vector<int> value) : Values(value) {}
};

Есть ли название для этой закономерности/проблемы? Есть ли библиотека метапрограммирования, в которой есть шаблоны, которые могут генерировать для меня оба определения? Как избежать повторного написания определений моих классов?


person bfair    schedule 12.11.2014    source источник
comment
Я не уверен, что понимаю, что вы имеете в виду под статической привязкой в ​​этом контексте; а что не так с constexpr auto pseudostatic = A<true>(5);?   -  person dyp    schedule 12.11.2014
comment
Как определить, создается ли конкретный класс во время выполнения?   -  person    schedule 12.11.2014
comment
Даже если решение dyp неприменимо напрямую, constexpr, вероятно, является ключом к тому, чтобы обойти проблему. Возможно, вам даже не понадобится делать A шаблон.   -  person MSalters    schedule 12.11.2014


Ответы (1)


Существует простой механизм для получения частей, которые не зависят от проблемы динамического/статического значения, в одном месте: поместите их в другой класс, назовем его basic_A, и давайте назовем контейнер статических/динамических значений, который вы указали в вопросе. value_A. Существуют разные способы соединения value_A и basic_A для формирования полного класса A:

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

  2. Агрегация value_A внутри basic_A. Вы должны были бы либо сделать basic_A тоже шаблоном, только передать параметры в value_A и предоставить правильные конструкторы для обеих специализаций, вероятно, каким-то образом отключив и включив их через SFINAE. Не очень красивый и поддерживаемый кусок кода. В качестве альтернативы можно было бы создать общий базовый класс (интерфейс) для двух специализаций value_A, иметь unique_ptr для этого интерфейса в basic_A и передать готовый сконструированный value_A в конструктор basic_A за счет вызова виртуальной функции и косвенного обращения всякий раз, когда вы хотите получить доступ к значению. Фу, особенно если A задуман как небольшой и быстрый легкий класс.

  3. Наследовать basic_A от value_A. Те же проблемы, что и в 2., касаются конструкторов и пересылки параметров шаблона.

  4. Наследовать value_A от basic_A. Проблема построения исчезла, но теперь basic_A не может легко получить доступ к значению value_A. Одним из решений было бы иметь чистую виртуальную функцию getValue() в basic_A, которую должны реализовать две специализации value_A. Это опять-таки связано с затратами на отправку виртуальной функции, что может быть нежелательно для небольшого облегченного класса, но позволяет инкапсулировать, поскольку basic_A не является шаблоном и может скрыть свою реализацию в файле .cpp. Другим подходом было бы использование полиморфизма времени компиляции через CRTP, что снова сделало бы basic_A шаблоном.

Вот два примера для двух подходов 4.:

4a: getValue() как виртуальная функция:

//basic_a.hpp

struct basic_A {
  int foo() const;
  virtual int getValue() const = 0;
};

//basic_A.cpp
int basic_A::foo() const { return 10 * getValue(); }

4b: getValue() через CRTP

template <class Value_t>
struct basic_A {
  int foo() const { return 10 * value_(); }
private:
  int value_() const { return static_cast<Value_t const&>(*this).getValue(); }
};

Шаблон A ака. A_value для 4b следует. Для 4a это почти то же самое, просто уберите аргументы шаблона и скобки из basic_A, так как это простой класс:

template <bool dyn, int value = 0> 
struct A;

template <>
struct A<true, 0> : basic_A<A<true, 0>>
{
  int val;
  int getValue() const { return val; }
};

template <int value>
struct A<false, value> : basic_A<A<false,value>>
{
  int geValue() { return value; }
};
person Arne Mertz    schedule 12.11.2014
comment
basic_A в качестве базового класса также может быть шаблоном CRTP для доступа к значению без вызова виртуальной функции. - person aschepler; 12.11.2014
comment
@aschepler это именно то, что показывает мой последний пример. Я немного реструктурирую ответ, чтобы подчеркнуть обе возможности, прежде чем показывать код. - person Arne Mertz; 12.11.2014