9

I have a structure that I pass around my application which contains a bunch of callback functions:

typedef struct {
    std::function<void (void)>    f1;
    std::function<void (int)>     f2;
    std::function<int  (float *)> f3;
    // ... and so on
} CallbackTable;

I handle state control within the application by binding different functions to the various callbacks, depending upon the current system state; it works fine.

What I'd now like to do is to add a couple of extra callbacks with signatures containing variable numbers of arguments, akin to printf: for example,

    std::function<int  (const char * format, ...)> printToStdOut;

This doesn't work. In Visual C++, I get an error message stating:

error C2027: use of undefined type 'std::_Get_function_impl<_Fty>'

I'm still feeling my way through this area of C++ syntax and would very much appreciate any advice on how I should proceed from here. My overriding objective is to be able to make a call along the lines of:

myCallbackTable.printToStdOut("I've just eaten %d bananas\r\n", nBananas);

...and to have the output directed to a console, to a file or to a GUI window, according to the system state.

Eos Pengwern
  • 1,467
  • 3
  • 20
  • 37
  • If it is just output you are interested in you should use an [`std::ostream`](http://www.cplusplus.com/reference/ostream/ostream/ostream/) instead. – nwp Sep 04 '14 at 17:33
  • `std::function` is only defined for function types of the form `R(ArgTypes...)`, where `ArgTypes...` is a pack expansion of a type parameter pack (i.e., a (possibly empty) list of types). So, no C-style variadic functions allowed. – T.C. Sep 04 '14 at 17:34
  • AFAIK no specialization of `std::function` for variadic functions, see a possible work around [here](https://www.daniweb.com/software-development/cpp/threads/467086/stdfunction-with-several-or-none-parameters#post2034402), not sure if its related. – P0W Sep 04 '14 at 17:58

2 Answers2

5

Edit: Original answer was wrong, modified but still may not be a good answer, leaving it here for educational purposes:

Many variable argument functions have a va_list version. printf for example has vprintf. These variants explicitly take a va_list instead of an ellipses.

#include <iostream>
#include <functional>
#include <cstdarg>


int main()
{
   std::function<int(const char*,va_list)> test2(vprintf);

   return 0;
}

Invoking it, however, is a pain.

int invoke_variadic(const std::function<int(const char*,va_list)>& ref,const char* a ,...)
{
    va_list args;
    va_start(args,a);
    ref(a,args);
    va_end(args);
}

What was wrong with original post (thanks /u/T.C.): std::function<int(const char*,va_list)> test2(printf) compiles, which I took to meaning it "worked". However it compiles because printf can accept arguments of any type (including a va_list), and std::function only checks if it can be invoked in that way.

IdeaHat
  • 7,641
  • 1
  • 22
  • 53
  • So does `std::function test2(printf);`, or even `std::function test2(printf);`. It compiles because `...` can match anything, not because it's somehow a synonym for `va_list` (it most certainly is not). – T.C. Sep 04 '14 at 17:53
  • @T.C. You are right...I cannot explain why all of it compiles. I'm still not exactly sure what is going on, but it doesn't actually take a va_list...there is a va_list variant of printf, however, so I'm changing my answer to reflect that. Got any explanation on why it accepts that? It does, indeed, "work" after defining the input types... – IdeaHat Sep 04 '14 at 17:58
  • It works because you *can* indeed invoke `printf` with that list of arguments and get back something implicitly convertible to `int` (assuming of course that there's no UB caused by wrong format strings etc.). `std::function` doesn't actually care about the type of the thing it is storing, only that it can be invoked in the way specified in the template parameter. – T.C. Sep 04 '14 at 18:00
0

Following on from what @MadScienceDreams suggested, this has worked rather well for me...

Firstly, I defined variable-argument versions of the std::function objects in the struct, then used the fact that this is C++ not C to add some methods as well:

typedef struct {
    std::function<void (void)>    f1;
    std::function<void (int)>     f2;
    std::function<int  (float *)> f3;
    // ... and so on

    std::function<int  (const char * format, va_list args)> vprintToStdOut; 
    std::function<int  (const char * format, va_list args)> vprintToStdErr;

    int printToStdOut(const char * format, ...)
    {
        va_list ap;
        va_start(ap, format);
        int count = vprintToStdOut(format, ap);
        va_end(ap);
        return count;
    }

    int printToStdErr(const char * format, ...)
    {
        va_list ap;
        va_start(ap, format);
        int count = vprintToStdErr(format, ap);
        va_end(ap);
        return count;
    }

} CallbackTable;

The default implementations of the variable-argument functions, for console output, were then (after having declared 'using namespace std::placeholders;' without which the syntax is unmanageable):

myCallbackTable.vprintToStdOut = std::bind(vfprintf, stdout, _1, _2);
myCallbackTable.vprintToStdErr = std::bind(vfprintf, stderr, _1, _2);

...and from this point, I can use exactly the syntax I was hoping to use:

myCallbackTable.printToStdOut("I like to eat at least %d bananas a day\r\n", nBananas);
Eos Pengwern
  • 1,467
  • 3
  • 20
  • 37