среда, 18 июня 2014 г.

[prog.c++] Определение сигнатуры лямбда-функции

С++11 -- это, конечно, мощная штука. Но для некоторых вещей все же, как и в старые добрые времена, нужно использовать трюкачество. Один из подобных трюков я нашел на StackOverflow, когда мне потребовалось определить сигнатуру лямбда-функции (т.е. выделить тип результирующего значения и тип параметра). Решение подсказано Энтони Уильямсом. Продублирую его здесь, дабы никуда ходить не нужно было. Так же следует обратить внимание на предупреждение о применимости этого подхода:

#include <iostream>

template<typename F,typename R>
void do_stuff(F& f,R (F::*mf)() const)
{
    (f.*mf)();
}

template<typename F,typename R,typename A1>
void do_stuff(F& f,R (F::*mf)(A1) const)
{
    (f.*mf)(99);
}

template<typename F,typename R,typename A1,typename A2>
void do_stuff(F& f,R (F::*mf)(A1,A2) const)
{
    (f.*mf)(42,123);
}

template<typename F>
void do_stuff(F f)
{
    do_stuff(f,&F::operator());
}

int main()
{
    do_stuff([]{std::cout<<"no args"<<std::endl;});
    do_stuff([](int a1){std::cout<<"1 args="<<a1<<std::endl;});
    do_stuff([](int a1,int a2){std::cout<<"2 args="<<a1<<","<<a2<<std::endl;});
}

Предупреждение: данный код не работает с функциональными типами (т.е. с указателями на функции); с классами, в которых более одного operator(); с классами, у которых operator() не константен.

Небольшое пояснение сути этого трюка. Лямбда в C++11 -- это объект какого-то сгенерированного компилятором типа. Компилятор умеет приводить лямбду к std::function, но лямбда -- это не экземпляр std::function. При этом у лямбды есть operator() const, тип которого можно попытаться диагностировать показанным выше способом. Обычно это так. Но, если лямбда определяется с ключевым словом mutable, то у нее будет operator() без const. И показанный выше подход должен быть доработан так, чтобы были варианты do_stuff, принимающие, скажем, не R (F::*mf)(A1,A2) const, а R (F::*mf)(A1,A2):

template<typename F,typename R,typename A1,typename A2>
void do_stuff(F& f,R (F::*mf)(A1,A2) const)
{
    (f.*mf)(42,123);
}

template<typename F,typename R,typename A1,typename A2>
void do_stuff(F& f,R (F::*mf)(A1,A2))
{
    (f.*mf)(24,321);
}
...
    do_stuff([](int a1,int a2){std::cout<<"2 args="<<a1<<","<<a2<<std::endl;});
    do_stuff([](int a1,int a2) mutable {std::cout<<"2 args="<<a1<<","<<a2<<std::endl;});

При запуске будет получено:

2 args=42,123
2 args=24,321

Комментариев нет: