Сокрытие реализации вариационных шаблонов

У меня есть библиотека 3rdParty с таким методом:

bool Invoke(const char* method, Value* args, size_t nargs)

Он принимает массив своего внутреннего типа (конвертируемый в любые примитивные типы C++) и arg count в качестве своих внутренних параметров. В своем коде я написал некоторый обобщенный помощник, чтобы избежать ручного создания и преобразования типов для каждого вызова:

template<class ... Args>
bool WrappedValue::Invoke(const char* method, Args&& ... args)
{
    3rdParty::Value values[] = 
    {
        3rdParty::Value(std::forward<Args>(args))...
    }

    return m_value.Invoke(method, values, sizeof ... (Args));
}

Это работает просто отлично, но теперь у меня должен быть код 3rdParty, определенный в моих заголовочных файлах и lib, подключенный напрямую к моему основному проекту.

Можно ли скрыть детали реализации и использования этой сторонней библиотеки? (Используйте 3- ий идиома прыща или прокси-объект для 3rdParty:: Value). Я знаю, что невозможно использовать методы виртуальных шаблонов в C++ для создания прокси или просто переместить реализацию шаблонов в.cpp, поэтому я полностью застрял в этой проблеме.

Буду благодарен за любую помощь)

2 ответа

Решение

Конечно. Просто напишите эквивалент std::variant<int, double, char, every, other, primitive, type>,

Теперь ваш Invoke преобразует ваш args в массив (вектор, диапазон, что угодно) из этих вариантов.

Затем вы передаете этот массив вариантов вашему внутреннему методу Invoke.

Этот метод внутреннего вызова затем использует эквивалент std::visit генерировать 3rdParty::Value от каждого из ваших вариантов.

Повышение обеспечивает boost::variant что, вероятно, будет работать.

Вы также можете свернуть это вручную. Узко указав вашу проблему, вы получите что-то более простое, чем std::variant, Однако это было бы больше, чем работа.


Другой подход заключается в следующем

template<class T> struct tag_t {constexpr tag_t(){}; using type=T;};
template<class T> constexpr tag_t<T> tag{};

template<class T, class F, class ... Args>
bool WrappedValue::Invoke(tag_t<T>, F&& f, const char* method, Args&& ... args)
{
  T values[] = {
    T(std::forward<Args>(args))...
  };

  return std::forward<F>(f)(method, values, sizeof...(Args));
}

что проще. Здесь вы бы написали:

bool r = Invoke( tag<3rdParty::Value>, [&](const char* method, 3rdParty::Value* values, std::size_t count) {
  m_value.Invoke( method, values, count );
}, 3.14, 42, "hello world");

Если вы хотите избежать предоставления доступа к API 3rdParty, вам понадобится не шаблонный метод для передачи данных. Это неизбежно потребует некоторого механизма стирания типа (например, std::any), который вместо этого выставляется в вашем API.

Итак, да, вы могли бы сделать это, но тогда 3-я партия Value это уже метод стирания типа, и он будет передавать только данные из одного типа стирания в другой, создавая дополнительные издержки. Стоит ли платить такую ​​цену, решать только вам.

Я как-то упустил из виду ваше замечание, что все аргументы примитивны. В этом случае стирание типов намного проще и может быть выполнено через тег + объединение, например

struct erasure_of_primitive
{
  enum { is_void=0, is_str=1, is_int=2, is_flt=3, is_ptr=4 }
  int type = is_void;
  union {
    const char*s;  // pointer to external C-string
    int64_t i;     // any integer
    double d;      // any floating point number
    void*p;        // any pointer
  } u;

  erasure_of_primitive() = default;
  erasure_of_primitive(erasure_of_primitive&const) = default;
  erasure_of_primitive&operator=(erasure_of_primitive&const) = default;

  erasure_of_primitive(const char*str)
  : type(is_str), u.s(str) {}

  template<typename T> 
  erasure_of_primitive(T x, enable_if_t<is_integer<T>::value>* =0)
  : type(is_int), u.i(x) {}

  template<typename T> 
  erasure_of_primitive(T x, enable_if_t<is_floating_point<T>::value>* =0)
  : type(is_flt), u.d(x) {}

  template<typename T> 
  erasure_of_primitive(T*x)
  : type(is_ptr), u.p(static_cast<void*>(x)) {}
};
Другие вопросы по тегам