Може ли 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);

...и изходът да бъде насочен към конзола, към файл или към GUI прозорец, според състоянието на системата.


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
AFAIK няма специализация на 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
@T.C. Прав си...не мога да обясня защо всичко се компилира. Все още не съм съвсем сигурен какво се случва, но всъщност не се изисква va_list...има обаче va_list вариант на printf, така че променям отговора си, за да отразя това. Имате ли някакво обяснение защо приема това? Наистина работи след дефиниране на типовете вход... - person IdeaHat; 04.09.2014
comment
Работи, защото можете наистина да извикате printf с този списък от аргументи и да получите обратно нещо имплицитно конвертируемо в int (приемайки, разбира се, че няма UB, причинено от низове с грешен формат и т.н.). std::function всъщност не се интересува от типа на нещото, което съхранява, а само от това, че може да бъде извикано по начина, посочен в параметъра на шаблона. - person T.C.; 04.09.2014
comment
@T.C. Има смисъл. Актуализирах отговора си... - 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;

Реализациите по подразбиране на функциите с променливи аргументи за конзолен изход бяха тогава (след деклариране на „using namespace 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