On Github Dahko / cpp11slides
Быстрый способ создать функцию в точке ее использования
std::vector<int> v; … auto it = std::find_if(v.cbegin(), v.cend(), [](int i) { return i > 0 && i < 10; });
Генерирует примерно следующее:
class MagicType1 { public: bool operator()(int i) const { return i > 0 && i < 10; } }; auto it = std::find_if(v.cbegin(), v.cend(), MagicType1());
Быстрый способ создать функцию в точке ее использования
std::map<Solution*, int> mapSolID; ... int someSolId = ...; ... itInt = std::find_if( mapSolID.begin(), mapSolID.end(), ??? );
class IsEqualSolID { public: int m_nTarget; IsEqualSolID(int nTarget):m_nTarget(nTarget) {} bool operator ()(const std::pair<Solution*, int>& a) { return a.second == m_nTarget; } };
Быстрый способ создать функцию в точке ее использования
std::map<Solution*, int> mapSolID; ... int someSolId = ...; ... itInt = std::find_if( mapSolID.begin(), mapSolID.end(), IsEqualSolID(someSolId)) );
class IsEqualSolID { public: int m_nTarget; IsEqualSolID(int nTarget):m_nTarget(nTarget) {} bool operator ()(const std::pair<Solution*, int>& a) { return a.second == m_nTarget; } };
Быстрый способ создать функцию в точке ее использования
std::map<Solution*, int> mapSolID; ... int someSolId = ...; ... itInt = std::find_if(mapSolID.begin(), mapSolID.end(), [&someSolId](const std::pair<Solution*, int>& a) { return a.second = someSolId; } );
Функциональные объекты, задаваемые лямбдами, называются замыканиями (closures). Замыкания могут существовать вне исходногно контекста (scope):
std::function<bool(int)> returnClosure(int a) { int b, c; … return [](int x) { int a*x*x + b*x + c == 0; }; } auto f = returnClosure(10);
Вот тут
if(f(22) == true) { //... }
чему равны a, b, c? Му же уже вышли из returnClosure!
Со статическими и глобальными переменными все "нормально":
int a; std::function<bool(int)> returnClosure() { static int b, c; … return [](int x) { int a*x*x + b*x + c == 0; }; } auto f = returnClosure(10);
А локльные переменные надо специально захватывать (capture).
Захваченная переменная копируется в closure. Следующий код
{ int minVal; double maxVal; … auto it = std::find_if(v.cbegin(), v.cend(), [minVal, maxVal](int i) { return i > minVal && i < maxVal; } ); }
геренирует
class MagicType { public: MagicType(int v1, double v2): _minVal(v1), _maxVal(v2) {} bool operator()(int i) const { return i > _minVal && i > _maxVal; } private: int _minVal; double _maxVal; }; auto it = std::find_if(v.cbegin(), v.cend(), MagicType(minVal, maxVal));
Захваченная переменная копируется в closure. Следующий код
{ int minVal; double maxVal; … auto it = std::find_if(v.cbegin(), v.cend(), [&minVal, &maxVal](int i) { return i > minVal && i < maxVal; } ); }
геренирует
class MagicType { public: MagicType(int& v1, double& v2): _minVal(v1), _maxVal(v2) {} bool operator()(int i) const { return i > _minVal && i > _maxVal; } private: int& _minVal; double& _maxVal; }; auto it = std::find_if(v.cbegin(), v.cend(), MagicType(minVal, maxVal));
Можно прописать захват всех переменных по умолчанию:
auto it = std::find_if( v.cbegin(), v.cend(), // default is [=](int i) // by value { return i > minVal && i < maxVal; }); auto it = std::find_if( v.cbegin(), v.cend(), // default is [&](int i) // by ref { return i > minVal && i < maxVal; });
При этом можно указать другой способ захвата для отдельных переменных:
auto it = std::find_if(v.cbegin(), v.cend(), [=, &maxVal](int i) { return i > minVal && // minVal by value i < maxVal; }); // maxVal by reference
Тип возвращаемого значания указывать не нужно, если:
Иначе надо указывать явно через "trailing return type syntax":
std::vector<double> v; … std::transform(v.begin(), v.end(), v.begin(), [](double d) -> double { makeLogEntry("std::transform", d); return std::sqrt(std::abs(d)); } );
Возвращаемы тип вычисляется автоматически (для всех функций). Аргументы лямбд тоже вычисляются.
auto magicValue1(int seed) // both returns are int { if (seed >= 0) return seed; else return 42; } std::partition( d.begin(), d.end(), [] (auto val) // ->bool not required, val type automatic { if (val % 10 == 0) return true; return false; } );
Init captures (захват через инициализацию)
return [ minVal = computeMinVal(this->minSeed) ] (int x) { return minVal <= x };
Ранее мы рассматривали простые примеры, когда лямбда создается и сразу же используется в алгоритме stl.
std::vector<int> v; … auto it = std::find_if(v.cbegin(), v.cend(), [](int i) { return i > 0 && i < 10; });
Когда мы обсуждали захват переменных, было понятно, что захват по значению нужен именно для отложенного запуска лямбд.
Как же хранить лямбды?
??? fn = [](MyClass* x) { return x->IsGood(); }
auto fn = [](MyClass* x) { return x->IsGood(); }"Let's make it available everywhere!"
auto x1 = 10; // x1: int std::map<int, std::string> m; auto i1 = m.begin(); // i1: std::map<int, std::string>::iterator for(auto i = m.begin(); i!=m.end(); i++) { i->second+="!"; }
"auto" переменные имеют тип инициализирующего их выражения.
Можно добавлять const/volatile и reference/pointer:
const auto *x2 = &x1; // x2: const int* const auto& i2 = m; // i2: const std::map<int, std::string>& auto ci = m.cbegin(); // ci: std::map<int, std::string>::const_iterator
Выведение типа аналогично выведению типа в шаблонах.
std::function<int(std::string&)> f; // f refers to callable entity // compatible with given sig. int someFunc(std::string&); // some function f = someFunc; // f refers to someFunc f = [](std::string &s)->unsigned { s += "!"; return s.size(); }; // f refers to a closure class Gadget { public: int operator()(std::string&); // function call operator … }; Gadget g; f = g; // f refers to g
std::function - универсальное представление вызываемого объекта(callable entity)
bool myFn(int n, MyClass* obj) { //... } std::function<bool(int, MyClass*)> fn = myFn;
bool myFn1(std::string str) { //... } void myFn2(const char* str) { //... } std::function<void(std::string)> fn = myFn1; fn = myFn2;
class Button: public SomeGUIFrameworkBaseClass { public: … using CallbackType = std::function<void(short)>; void setCallback(const CallbackType& cb) { clickHandler = cb; } virtual void onClick(short upOrDown) // invoked by base class { clickHandler(upOrDown); // invoke function object } private: CallbackType clickHandler; };
Button b; b.setCallback([](int v) { logClick(v); });
В Palign.dll был класс CSimplex, делающий минимизацию по многим переменным. Он принимал на вход функцию, которую нужно было минимизировать, и варьировал ее аргументы до достижения минимума.
typedef double (*FuncToMinimize) (double*); class CSimplex { public: CSimplex(FuncToMinimize, int, double*, double, bool _bound = false, ...); ... };
В какой-то момент понадобилось передавать туда не функцию, а функтор, имеющий ссылки на другие объекты. В указатель на функцию такое в принципе не передашь - разве только вводить глобальные объекты.
public: CSimplex(std::function<double(double*)>, int, double*, double, bool _bound = false, ...);
Альтернативное решение - передавать указатель на функтор IFuncToMinimize с функцией virtual double operator() (double*)
Хранение генерирующих функций фабрики
class CReflectSeriazableFactoryFace { public: CReflectSeriazableFactoryFace(); virtual ISerializableFace* MakeSerializableObject( GUID const& serialId ); virtual unsigned ObjectTypesCount() { return m_map.size(); } virtual GUID GetObjectGUID(unsigned i); protected: std::map<GUID, std::function<IDocumentNode*()>> m_map; }
CReflectSeriazableFactoryFace::CReflectSeriazableFactoryFace() { m_map[SERID_IPOLYHEDRONNode] = []{ return new CModelNodeImpl; }; m_map[SERID_IReflectDataNode] = []{ return new CReflectDataNode; }; } ISerializableFace* CReflectSeriazableFactoryFace::MakeSerializableObject( GUID const& serialId ) { auto creator = m_map.find(serialId); if(creator == m_map.end()) return NULL; IDocumentNode *node = creator->second(); if(!node) return NULL; CDocumentNodeCommonSerializer * wrapper = new CDocumentNodeCommonSerializer(node); return wrapper; }
Callback после чтения объекта сериализатором из файла
new CPolyhedronSer(m_phModel, [this](POLYHEDRON* model) { this->SetPOLYHEDRONByCloning(model); delete model; } );
std::map<int, std::string> m; typedef m::value_type MyMapVal; for(auto i = m.begin(); i!=m.end(); i++) { i->second+="!"; } std::foreach(m.begin(), m.end(), [](MyMapVal & i) { i.second+="!"; }
Чем второй вариант може быть лучше первого?
В случае с лямбдой мы работаем не с итератором, а со значением, так что не можем "испортить" итератор.
for(auto i = m.begin(); i!=m.end(); i++) { i->second+="!"; i++; } std::foreach(m.begin(), m.end(), [](MyMapVal & i) { i.second+="!"; /*i++;*/ }
Функциональный стиль - максимально ограничиваем множество доступных коду данных, чтобы минимизировать побочные эффекты. В идеале вообще без переменных. Такой код надежнее и легче тестируется.
Коду внутри лямбд доступны только их параметры и захваченные переменные (поэтому лучше избегать [&] и [=]).
Примеров испльзования лямбд в STL-алгоритмах выше было множество.
С приходом лямбд можно расширить количество алгоритмов, которые мы повседневно используем, а не писать циклы вручную.
Неплохой язык для знакомства с функциональный программированием
Ресурсы:
Переменная не должна меняться по смыслу, но ее иницаилизация не укладывается в a = expr.
const auto sortedInts = []()->std::vector<int> { // init const vector std::vector<int> v(NUM_VALUES); // w/NUM_VALUES std::iota(v.begin(), v.end(), 0-NUM_VALUES/2); // sequential ints return v; // centered at 0 }(); const auto priority = [=]()->Priority { makeLogEntry("Initializing priority"); auto cInfo = getCustomerInfo(customerID); Priority p = (cInfo.salesInLast12Months() < bonusThreshhold) ? normalPriority : highPriority; return p; }();
Это тоже трюк из функционального программирования - там по-другому нельзя.
class IDocumentNode : public IOctInterface { public: // List of sub-nodes virtual unsigned ChildCount() const = 0; virtual IDocumentNode* Child(unsigned i) = 0; virtual void AddChild(IDocumentNode * pChild) = 0; ... };
class TDocumentNodeLeaf : public IDocumentNode { public: virtual size_t ChildCount() const { return 0;} ; virtual IDocumentNode* Child(size_t i) { return NULL; }; virtual void AddChild(IDocumentNode *) { return; } ... }; class TDocumentNodeFolder : public TDocumentNodeLeaf { public: virtual size_t ChaildCount() const { return m_vChilds.size(); } virtual IDocumentNode* Child(int i) { return m_vChilds[i]; }; ... }
TDocumentNodeFolder obj; obj.AddChild(something); std::cout << obj.ChildCount(); // prints 0 !!!
class IDocumentNode : public IOctInterface { public: // List of sub-nodes virtual unsigned ChildCount() const = 0; virtual IDocumentNode* Child(unsigned i) = 0; virtual void AddChild(IDocumentNode * pChild) = 0; ... };
class TDocumentNodeLeaf : public IDocumentNode { public: virtual size_t ChildCount() const override { return 0;} ; virtual IDocumentNode* Child(size_t i) override { return NULL; }; virtual void AddChild(IDocumentNode *) override { return; } ... }; class TDocumentNodeFolder : public TDocumentNodeLeaf { public: virtual size_t ChaildCount() const override { return m_vChilds.size(); } virtual IDocumentNode* Child(int i) override { return m_vChilds[i]; }; ... }
error C3668: 'TDocumentNodeFolder::ChaildCount' : method with override specifier 'override' did not override any base class methods