Создавайте динамически заканчивающиеся нулем параметры для привязки C

Для моего проекта я привязываю метод C++ к функции C. Эта функция от gstreamer и имеет переменную форму в следующем виде:

GstElement *gst_element_link_many(GstElement *el1, GstElement* el2 ,[...], nullptr);

Допустим, я хочу передать вектор своим привязкам, каков будет ваш подход? В идеале у меня был бы такой результат

void linkElements(std::vector<GstElement*>& elements) {
    [...]
    gst_element_link_many(elementList... , nullptr);
}

Я думаю о пакетах параметров, но не знаю, как их реализовать.

Спасибо !

Изменить: я не могу изменить функцию gstreamer, поэтому не могу передать указатель. Gstreamer как gst_element_link(GstElement* el1, GstElement *el2); функция, но она ведет себя по-другому, потому что связывает элементы 2 на 2 и, таким образом, вычисляет возможности каждой пары независимо.


person Emmanuel Ruaud    schedule 01.04.2019    source источник
comment
Ваши векторы ограничены по длине?   -  person StoryTeller - Unslander Monica    schedule 01.04.2019
comment
Они не. На практике я могу привести от 5 до 10 аргументов.   -  person Emmanuel Ruaud    schedule 01.04.2019
comment
Возможный дубликат Передача всех элементов массив в функцию с переменными параметрами (...)   -  person Michael Veksler    schedule 01.04.2019
comment
C++ имеет гораздо больше возможностей, чем простой C, я уверен, что должно быть лучшее решение.   -  person Emmanuel Ruaud    schedule 01.04.2019
comment
Насколько я понимаю, предлагаемое решение состоит в том, чтобы повторить и свести его к функции между двумя аргументами, которая работает для суммы, но не для gst_element_link_many, потому что для правильного вычисления требуется все количество аргументов за один раз.   -  person Emmanuel Ruaud    schedule 01.04.2019
comment
Есть лучший дубликат: Динамическая передача параметров функциям с переменным числом аргументов. В нем упоминается библиотека avcall, которая делает это во время выполнения.   -  person Michael Veksler    schedule 01.04.2019


Ответы (1)


Пакеты параметров представляют собой конструкцию времени компиляции, а вектор — конструкцию времени выполнения. Это делает пакеты параметров неактуальными для этого вопроса. Есть несколько решений, если не считать переделки интерфейса функции C.

Первый вариант дан в ответе M Oehm на Передача всех элементов массива в функцию с переменными параметрами ( …) упоминает технику одного большого переключателя:

void linkElements(std::vector<GstElement*>& elements) {
    switch (elements.size()) {
      case 0: return gst_element_link_many(nullptr);
      case 1: return gst_element_link_many(elements[0], nullptr);
      case 2: return gst_element_link_many(elements[0], elements[1], nullptr);
      case 3: return gst_element_link_many(elements[0], elements[1], elements[2], nullptr);
      case 4: return gst_element_link_many(elements[0], elements[1], elements[2], elements[3], nullptr);
      ... and so on for how long one wants to support
      default:
         throw std::runtime_error(std::to_string(elements.size()) + " elements can't be passed (too many elements"));
}

Недостатком является то, что этот метод определяет максимальное количество параметров во время компиляции.

Второй вариант — автоматизировать оператор switch. Он использует рекурсию, поэтому он может быть менее эффективным, чем другие варианты, но его очень легко расширить до большего количества параметров:

#include <iostream>
#include <string>
#include <cstdio>
#include <vector>
#include <utility>
#include <tuple>

template <unsigned size, class Func, class Type, std::size_t... I>
void call_n(Func func, const std::vector<Type> & vec, std::index_sequence<I...>)
{
    func(vec[I]...);
}
template <unsigned size, class Func, class Type>
auto call_n(Func func, const std::vector<Type> & vec)
{
    return call_n<size>(func, vec, std::make_index_sequence<size>());
}

template <unsigned min, unsigned max, class Func, class Type>
void call_max_n(Func func, std::vector<Type> & elements)
{
    if (elements.size() == min) {
        call_n<min>(func, elements);
        return;
    }
    if constexpr(min < max)
        call_max_n<min+1, max>(func, elements);
    else
        throw std::runtime_error("Too many elements");
}

int main()
{
    std::vector<const char*> elements{"%s %s %s", "hello", "nice", "world"};
    call_max_n<1, 4>(std::printf, elements);
}

Вы можете попробовать его на wandbox. Из моих тестов gcc может создать плоскую функцию. Возможно, перед более сложными примерами он действительно будет использовать рекурсию, но, несмотря ни на что, сложность составляет O (n), как если бы он был вызван без какой-либо рекурсии.

(EDIT: заменен алгоритм O(n2) на линейный алгоритм показано выше).

Третий вариант приведен в ответе Мэтта Джойнера на "Динамическая передача параметров функциям с переменным числом элементов". библиотека C, которую можно использовать для преобразования вектора в вариативные шаблоны:

FFCALL — это библиотека, которая предоставляет оболочки для динамической передачи параметров в функции с переменным числом аргументов. . Вас интересует группа функций avcall.

Приведенные выше ссылки устарели, и эта ссылка кажется более актуальной.

Насколько я понимаю документацию, ваш код должен выглядеть так:

#include <avcall.h>
void linkElements(std::vector<GstElement*> & elements) {
    av_alist alist;
    av_start_void(alist, &gst_element_link_many);
    for (auto ptr: elements) {
       av_ptr(alist, GstElement*, ptr);
    }
    av_ptr(alist, GstElement*, nullptr);
    av_call(alist);
}

Я не уверен, насколько это портативно. Кажется, он работает на машинах Intel с Linux (как 32-битные, так и 64-битные). Возможно, это также может работать в Windows. Если он не работает на вашей системе, то я думаю, что портировать его на вашу систему не так уж и сложно.

И последний вариант — использовать ассемблер. Данные из массива можно поместить в правильные регистры и/или стек. Это не очень сложно, и его можно найти здесь для архитектуры Intel.

К сожалению, все гибкие решения не являются чисто C++ и требуют каких-либо дополнений (либо из библиотеки, либо из ассемблерного кода).

EDIT: я добавил одно из решений в github и Я намерен использовать все вышеперечисленные решения.

person Michael Veksler    schedule 01.04.2019
comment
@EmmanuelRuaud Я добавил еще одну опцию, которая автоматизирует первую опцию switch для любого количества параметров. Этот использует пакеты параметров. - person Michael Veksler; 01.04.2019
comment
Хороший вариант, в какой-то момент мне придется скомпилировать его на ARM, так что в конечном итоге я выберу именно его. - person Emmanuel Ruaud; 01.04.2019
comment
@EmmanuelRuaud У меня было еще одно редактирование - второй вариант выполнялся в линейном времени. - person Michael Veksler; 01.04.2019