Я работаю над некоторым библиотечным кодом и хочу, чтобы пользователи могли использовать преимущества статического связывания, если они могут. Если они не могут создать экземпляр класса во время компиляции, я хочу, чтобы была динамическая версия класса, чтобы его можно было создавать во время выполнения.
Для быстрого примера, скажем, у меня есть шаблон структуры 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) {}
};
Есть ли название для этого шаблона / проблемы? Есть ли библиотека метапрограммирования, которая имеет шаблоны, которые могут генерировать оба определения для меня? Как мне избежать необходимости писать определения моих классов дважды?
Существует простой механизм, позволяющий помещать части, которые не зависят от проблемы динамического / статического значения, в одно место: поместить их в другой класс, назовем его basic_A
, и давайте назовем контейнер статических / динамических значений, который вы показываете в вопросе value_A
, Есть разные способы подключения value_A
а также basic_A
сформировать полный A
учебный класс:
Агрегация basic_A
внутри value_A
, Это будет означать, что вы должны направить каждый метод basic_A
через value_A
и предоставить соответствующие однострочники в обеих специализациях value_A
, Это, вероятно, не слишком выгодно, потому что вы должны продублировать все однострочники, так что поцарапайте это.
Агрегация value_A
внутри basic_A
, Вы должны были бы либо сделать basic_A
шаблон также только для передачи параметров value_A
и предоставить правильные конструкторы для обеих специализаций, возможно, как-то отключив и включив их через SFINAE. Не очень красивый и обслуживаемый кусок кода. Альтернативой может быть создание общего базового класса (интерфейса) для двух специализаций value_A
есть unique_ptr
к этому интерфейсу в basic_A
и передать готовый построенный value_A
в basic_A
конструктор, за счет вызова виртуальной функции и перенаправления poitner всякий раз, когда вы хотите получить доступ к значению. Фу, особенно если A
предназначен для небольшого и быстрого легкого класса.
унаследовать basic_A
от value_A
, Те же проблемы, что и в 2., применяются в отношении конструкторов и пересылки параметров шаблона.
унаследовать value_A
от basic_A
, Проблема строительства исчезает, но сейчас basic_A
не может легко получить доступ value_A
ценность. Одним из решений было бы иметь чисто виртуальную функцию getValue()
в basic_A
которые две специализации value_A
должны реализовать. Это опять-таки имеет стоимость отправки виртуальной функции, которая может быть нежелательна для небольшого легкого класса, но она позволяет инкапсуляцию, так как basic_A
не является шаблоном и может скрывать свою реализацию в файле .cpp. Другой подход заключается в использовании полиморфизма во время компиляции через CRTP, который сделает basic_A
шаблон снова.
Вот два примера для двух подходов из 4:
4а: 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
за 4б следует. Для 4а это почти то же самое, просто потерять аргументы шаблона и скобки из 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; }
};