Asked  7 Months ago    Answers:  5   Viewed   68 times

In C, is it possible to forward the invocation of a variadic function? As in,

int my_printf(char *fmt, ...) {
    fprintf(stderr, "Calling printf with fmt %s", fmt);
    return SOMEHOW_INVOKE_LIBC_PRINTF;
}

Forwarding the invocation in the manner above obviously isn't strictly necessary in this case (since you could log invocations in other ways, or use vfprintf), but the codebase I'm working on requires the wrapper to do some actual work, and doesn't have (and can't have added) a helper function akin to vfprintf.

[Update: there seems to be some confusion based on the answers that have been supplied so far. To phrase the question another way: in general, can you wrap some arbitrary variadic function without modifying that function's definition.]

 Answers

76

If you don't have a function analogous to vfprintf that takes a va_list instead of a variable number of arguments, you can't do it. See http://c-faq.com/varargs/handoff.html.

Example:

void myfun(const char *fmt, va_list argp) {
    vfprintf(stderr, fmt, argp);
}
Tuesday, June 1, 2021
 
tika
answered 7 Months ago
25

Your choice is either leave it as it is and use va_list, alias it (if it's GCC) as others pointed out, or do something along the lines of exec(2) interface - passing an array of pointers requiring a NULL terminator:

/* param args  NULL-terminated array of
 *              pointers to arguments.
 */
void doStuff( void* args[] );

Either way it would be much better to refactor the interface to somehow take advantage of the type system - maybe overload on exact argument types used:

void doStuff( int );
void doStuff( const std::string& );
void doStuff( const MyFancyAppClass& );

Hope this helps.

Thursday, August 5, 2021
 
Damjan Krowalka
answered 4 Months ago
23

Functions with ellipsis in C++ is only for compatibility with C. Using C++ I'd return temporary helper object in Call function and add template operator% to pass variable number of arguments. To use it in the following way:

cDelegateCaller.Call() % 2 % "Hello" % sString; // dummy argument isn't required

As to your question, Standard requires to invoke va_start before any access to the unnamed arguments. And va_start requires second argument which is the identifier of the rightmost parameter in the variable parameter list in the function definition.

Thursday, August 19, 2021
 
scessor
answered 4 Months ago
100

If I got it correctly, you need no fancy template magic to do this composition. Here is the almost self-explanatory code:

#include <functional>
#include <string>
#include <iostream>

// it is just an std::function taking A and returning B
template <typename A, typename B>
using Arrow = std::function<B(const A&)>;

// composition operator: just create a new composed arrow
template <typename A, typename B, typename C>
Arrow<A, C> operator*(const Arrow<A, B>& arr1, const Arrow<B, C>& arr2)
{
    // arr1 and arr2 are copied into the lambda, so we won't lose track of them
    return [arr1, arr2](const A& a) { return arr2(arr1(a)); };
}

int main()
{
    Arrow<int, float> plusHalf([](int i){return i + 0.5;});
    Arrow<float, std::string> toString([](float f){return std::to_string(f);});

    auto composed = plusHalf * toString; // composed is Arrow<int, std::string>
    std::cout << composed(6) << std::endl; // 6.5

    //auto badComposed = toString * plusHalf; // compile time error
}

I mostly played with lambda functions here.

Using a single function call instead of a operator chain proved to be a more tricky problem. This time you got some templates:

#include <functional>
#include <string>
#include <iostream>

// it is just an std::function taking A and returning B
template <typename A, typename B>
using Arrow = std::function<B(const A&)>;

// A helper struct as template function can't get partial specialization
template <typename... Funcs>
struct ComposerHelper;

// Base case: a single arrow
template <typename A, typename B>
struct ComposerHelper<Arrow<A, B>>
{
    static Arrow<A, B> compose(const Arrow<A, B>& arr)
    {
        return arr;
    }
};

// Tail case: more arrows
template <typename A, typename B, typename... Tail>
struct ComposerHelper<Arrow<A, B>, Tail...>
{
    // hard to know the exact return type here. Let the compiler figure out
    static auto compose(const Arrow<A, B>& arr, const Tail&... tail)
    -> decltype(arr * ComposerHelper<Tail...>::compose(tail...))
    {
        return arr * ComposerHelper<Tail...>::compose(tail...);
    }
};

// A simple function to call our helper struct
// again, hard to write the return type
template <typename... Funcs>
auto compose(const Funcs&... funcs)
-> decltype(ComposerHelper<Funcs...>::compose(funcs...))
{
    return ComposerHelper<Funcs...>::compose(funcs...);
}

using namespace std;

int main()
{
    Arrow<int, float> plusHalf([](int i){return i + 0.5;});
    Arrow<float, string> toString([](float f){return to_string(f);});
    Arrow<string, int> firstDigit([](const string& s){return s[0]-'0';});

    auto composed = compose(plusHalf, toString, firstDigit);
    // composed is Arrow<int, int>

    std::cout << composed(61) << std::endl; // "6"

    //auto badComposed = compose(toString, plusHalf); // compile time error
}
Friday, September 17, 2021
 
Ergwun
answered 3 Months ago
16

You don't need to concatenate Args2... and Args1..., and you shouldn't, since doing to makes it makes it impossible to tell where Args2... ends and Args1... begins. The way to pass multiple variadic arguments in a way that allows them to be extracted individually is wrapping them in yet another template: given a variadic template my_list, you could structure your my_convertible to be called as

my_convertible<my_list<Args2...>, my_list<Args1...>>

The standard library already has a variadic template that works well here: tuple. Not only that, but tuple<Args2...> is convertible to tuple<Args1...> if and only if Args2... are convertible to Args1..., so you can just write:

std::is_convertible<std::tuple<Args2...>, std::tuple<Args1...>>

Note: in the comments, @zatm8 reports that this doesn't always work: std::is_convertible<std::tuple<const char *&&>, std::tuple<std::string &&>>::value is reported as false, but std::is_convertible<const char *&&, std::string &&>::value is reported as true.

I believe that this is a bug, that they should both be reported as true. The problem is reproducible on http://gcc.godbolt.org/ with clang 3.9.1. It is not reproducible with gcc 6.3, and it is also not reproducible with clang 3.9.1 when using -stdlib=libc++. It appears that libstdc++ is using a language feature that clang doesn't quite handle correctly, and reducing it to a short example that does not rely on standard library headers gives:

struct S {
  S(const char *) { }
};
int main() {
  const char *s = "";
  static_cast<S &&>(s);
}

This is accepted by gcc, but rejected by clang. It has been reported in 2014 as https://llvm.org/bugs/show_bug.cgi?id=19917.

It appears that this has been fixed in late 2016, but the fix has not yet made it into a released version: http://lists.llvm.org/pipermail/cfe-commits/Week-of-Mon-20161031/175955.html

If you are affected by this, you may wish to avoid std::tuple and use @Yakk's answer instead.

Tuesday, October 12, 2021
 
Bram
answered 2 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :
 
Share