Можно ли использовать std::function для хранения функции с вариативными аргументами

У меня есть структура, которую я передаю по моему приложению, которая содержит кучу функций обратного вызова:

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

Я управляю состоянием в приложении, привязывая различные функции к различным обратным вызовам в зависимости от текущего состояния системы; это работает нормально.

Теперь я хотел бы добавить пару дополнительных обратных вызовов с сигнатурами, содержащими переменное количество аргументов, подобно printf: например,

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

Это не работает. В Visual C++ я получаю сообщение об ошибке:

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

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

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

... и направлять вывод на консоль, в файл или в окно графического интерфейса, в зависимости от состояния системы.


person Eos Pengwern    schedule 04.09.2014    source источник
comment
Если вас интересует только вывод, вместо него следует использовать std::ostream. .   -  person nwp    schedule 04.09.2014
comment
std::function определяется только для типов функций формы R(ArgTypes...), где ArgTypes... — это пакетное расширение пакета параметров типа (т. е. (возможно, пустой) список типов). Таким образом, никакие вариативные функции в стиле C не допускаются.   -  person T.C.    schedule 04.09.2014
comment
Насколько я знаю, нет специализации std::function для вариативных функций, см. возможную работу вокруг здесь, не уверен, что это связано.   -  person P0W    schedule 04.09.2014


Ответы (2)


Изменить: исходный ответ был неправильным, измененным, но все же не может быть хорошим ответом, оставив его здесь для образовательных целей:

Многие функции с переменными аргументами имеют версию va_list. printf например имеет vprintf. Эти варианты явно принимают va_list вместо многоточия.

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


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

   return 0;
}

Однако вызвать его — боль.

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

Что было не так с исходным сообщением (спасибо /u/T.C.): std::function<int(const char*,va_list)> test2(printf) компилируется, что я понял, что это "сработало". Однако он компилируется, потому что printf может принимать аргументы любого типа (включая va_list), а std::function только проверяет, может ли он быть вызван таким образом.

person IdeaHat    schedule 04.09.2014
comment
Как и std::function<int(const char*,int)> test2(printf);, или даже std::function<int(const char*,int, double, float)> test2(printf);. Он компилируется, потому что ... может соответствовать чему угодно, а не потому, что это какой-то синоним va_list (наверняка это не так). - person T.C.; 04.09.2014
comment
@Т.С. Вы правы... Я не могу объяснить, почему все это компилируется. Я до сих пор не совсем уверен, что происходит, но на самом деле он не требует va_list ... однако есть вариант va_list для printf, поэтому я меняю свой ответ, чтобы отразить это. Есть какое-нибудь объяснение, почему он это принимает? Это действительно работает после определения типов ввода... - person IdeaHat; 04.09.2014
comment
Это работает, потому что вы можете действительно вызывать printf с этим списком аргументов и возвращать что-то неявно конвертируемое в int (при условии, конечно, что нет UB, вызванного неправильными строками формата и т. д.). std::function на самом деле не заботит тип хранимой вещи, а только то, что она может быть вызвана способом, указанным в параметре шаблона. - person T.C.; 04.09.2014
comment
@Т.С. Имеет смысл. Я обновил свой ответ... - person IdeaHat; 04.09.2014

Следуя тому, что предложил @MadScienceDreams, это сработало для меня довольно хорошо...

Во-первых, я определил версии объектов std::function с переменными аргументами в структуре, а затем использовал тот факт, что это C++, а не C, чтобы добавить некоторые методы:

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;

Реализации по умолчанию функций с переменным аргументом для вывода на консоль были тогда (после объявления «использования пространства имен std::placeholders;», без которого синтаксис неуправляем):

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

... и с этого момента я могу использовать именно тот синтаксис, который надеялся использовать:

myCallbackTable.printToStdOut("I like to eat at least %d bananas a day\r\n", nBananas);
person Eos Pengwern    schedule 04.09.2014