-1

For my project I am binding a C++ method to a C function. This function is from gstreamer and is variadic in this form :

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

Let's say I want to pass a vector to my bindings, what would be your approach ? Ideally I would have this result

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

I am thinking of parameter packs but I am unsure of how to implement those.

Thanks !

Edit: I can't modify the gstreamer function so I can't pass a pointer. Gstreamer as a gst_element_link(GstElement* el1, GstElement *el2); function but it behaves differently because it links elements 2 by 2 and thus computes capabilities of each pair independently.

  • Are your vectors limited in their length? – StoryTeller - Unslander Monica Apr 01 '19 at 08:05
  • They are not. In practice I can put from 5 to 10 arguments. – Emmanuel Ruaud Apr 01 '19 at 08:06
  • 2
    Possible duplicate of [Passing all elements of an array to a function with variable parameters (...)](https://stackoverflow.com/questions/26275163/passing-all-elements-of-an-array-to-a-function-with-variable-parameters) – Michael Veksler Apr 01 '19 at 08:14
  • C++ has a lot more options than plain C, I am sure there must be a better solution. – Emmanuel Ruaud Apr 01 '19 at 08:23
  • 1
    @EmmanuelRuaud: Have a look at this post: [How to pass vector elements as arguments to variadic template function?](https://stackoverflow.com/questions/34233797/how-to-pass-vector-elements-as-arguments-to-variadic-template-function) – P.W Apr 01 '19 at 08:30
  • From what I understand, the solution proposed is to iterate and reduce it to a function between two arguments, that works for a sum but not for gst_element_link_many because it needs the whole number of arguments in one go to compute correctly. – Emmanuel Ruaud Apr 01 '19 at 08:46
  • 1
    There is a better duplicate to look at: [Passing parameters dynamically to variadic functions](https://stackoverflow.com/q/1721655/4955498). It mentions [avcall library](https://www.haible.de/bruno/documentation/ffcall/avcall/avcall.html) that does that at runtime – Michael Veksler Apr 01 '19 at 09:07

1 Answers1

1

Parameter packs are a compile-time construct, while a vector is a runtime construct. This makes parameter packs irrelevant for this question. There are several solutions, short of redesigning the interface of the C function.

The first option is given in M Oehm's answer to Passing all elements of an array to a function with variable parameters (…) mentions the technique of one big switch:

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"));
}

The disadvantage is that this method defines the maximal number of parameters at compile time.

The second option is to automate the switch statement. It uses recursion, so it may be less efficient than the other options, but it is very easy to extend to more parameters:

#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);
}

You can try it out on wandbox. From my tests, gcc is able to create a flat function. Maybe fore more complicated examples it will actually use recursion but, regardless or that, the complexity is O(n) just as if it was called without any recursion.

(EDIT: replaced the O(n2) algorithm with the linear algorithm shown above).

The third option is given in Matt Joiner's answer to "Passing parameters dynamically to variadic functions" mentions a C library that can be used to convert a vector into variadic templates:

FFCALL is a library which provides wrappers for passing parameters dynamically to variadic functions. The group of functions you're interested in is avcall.

The above links are outdated, and this link seems to be more up to date.

From the way I understand the documentation, your code should look like:

#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);
}

I am not sure how portable this is. It seems to work on Linux Intel machines (both 32 and 64 bits). Maybe it can also work on Windows. If it does not work on your system, then I think it is not too difficult to port it to your system.

And the last option is to use assembly. It is possible to put the data from the array into the correct registers and/or the stack. This is not very complicated, and can be found here for the Intel architecture.

Unfortunately, all the flexible solutions are not purely C++ and require some add on (either from a library, or from an assembly code).

EDIT: I have added one of the solutions to github, and I intend to all all of the above solutions.

Michael Veksler
  • 8,217
  • 1
  • 20
  • 33