Skip to content

C++_Functions

Rico_Jia edited this page Feb 10, 2022 · 7 revisions

========================================================================

Functor

========================================================================

  1. A functor is an object that acts like a function, using the overloaded operator().
    struct greaters{
        greaters(){}
        operator()(int a, int b){return a > b;}
    }
    
    struct increment{
    private:
    int a
    public:
        increment(int a){a = a;}
        operator()(int b){return b+a}
    }
    
    std::transform(arr, arr+n, arr, increment(a));      //equivalent to calling increment(a) first.
    std::make_heap(arr, arr+n, greaters());         //calls constructor first, then inside the function, it will call the overloaded().
    • Above is a cool use of functor, too

========================================================================

Lambda Expression

======================================================================== 0. An anonymous function object (or functor).

  1. Basic structure is

    [capture](inputs)->return_type`{
        return something
    }
    //Capture is capturing a variable in the current scope (inside a vector is not a scope). capture a copy[=], a reference[&], a specific variable [var]
    // return type is optional.
    1. An example is
    auto get_point = [range_vec](unsigned int index)-> rigid2d::Vector2D {
      double theta = rigid2d::PI*2.0* static_cast<double>(index)/360.0;
      double range = range_vec[index];
      double x = range * sin(theta);
      double y = range * cos(theta);
      rigid2d::Vector2D point (x,y);
      return point;
    };          // you only get a functor here
    1. capture by value:
    double data;
    auto func = [=](int i){};     //default
    auto func = [data](){}
    1. Capture by reference

      [=, &i]{};      // OK: by-copy capture, except i is captured by reference
      • lambda reference capture: the reference will be copied inside the closure.
        auto lam = [&]{}; 
        std::thread th1 (lam);    // the reference is copied over too. 
      • capture by reference is non-const.
    2. Call a lambda in a ctor, or anywhere you want:

      // example 1
      const int i = []{return 1;}(); //note the ()
      
      //example 2
      class Foo{
          int i_; 
          Foo(): i_([]{return 1;}()){}
      }; 
  2. lambda expression can be used as predicate for std::find

    auto search_result = std::find_if(this->open_list.begin(), this->open_list.end(),
            [&id](const A_star_node& node){return id == node.prm_vertex.id; });
  3. Lambda expression's return type is required when you have recursive calls within itself.

    • You also need
      #include <functional>
      std::function <bool (args)>
  4. Lambda Expression Theory

    • In compilation, closure class is created from an lambda expression
    • In runtime, a closure object is created as an instance of closure class. It can be copied. can we capture? Yes, that's how a lambda works, Cpp insights
      int i = 1;
      [&i](int a){ printf("%d\n", a);}
      
      // equivalent to: 
      int i = 1;
      class __lambda_12_16
      {
      public: 
      inline /*constexpr */ void operator()(int a) const
      {
        printf("%d\n", a);
      }
      
      private: 
      int & i;
      
      public:
      __lambda_12_16(int & _i)
      : i{_i}
      {}
      
      };
      
      visit(vec, __lambda_12_16{i});
    • Empty parameter list () can be omitted, if there's no mutable, constexpr, noexcept, trailing types
      auto func = [vec]{return 1;};   //OK
      auto func = [vec]mutable{return 1;};    //wrong
    • Mutable Lambdas: the closure by default, sets capture-by-copy params reference-to-const, i.e, they're not modifiable
      • Because operator () inside the closure class is const, so the params are consts
      std::vector<int> vec= {1,2,3};
      auto func = [vec]{vec[1]=1000;};      //Wrong, vec is automatically const
      auto func = [vec]()mutable{vec[1]=1000;};      //compiles
      • Code from the body will be transferred into the operator() function.
      • Mutable Lambdas are "stateful" if you copy a lambda, i.e, the params are stored as a member variable, and change everytime we call it
        int x = 0;
        auto foo = [x] () mutable {
         /* "x" cannot be modified without keyword mutable. */
         x++; //OK
         return x;
        };
         // call foo
        std::cout << foo() << " ";    //get 1
        // assign foo to bar, they are both objects!!
        auto bar = foo;               //copied 1
        // call foo again
        std::cout << foo() << " ";    // get 2
        // call bar
        std::cout << bar() << " ";    //get 2
  5. Move an object into a closure

    • In C++14, there's an easy way to do it
      • Because in C++11, you cannot capture the result of an expression, but in C++14 you can, it's now called "generalized lambda capture"
        auto func = [pw = std::make_unique<Widget>()]{pw -> foo = 100; };    //OK, pw is a move-constructed copy
        auto func = [pw = std::move(widget_ptr)]{};    //OK
    • In C++11, you have to find substitutes
      1. Functor
      class Functor{
        public:
          using Ptr = std::unique_ptr<Widget>;
          explicit Functor(Ptr&& ptr) : ptr_(std::move(ptr)){}    //you need std::move to transfer ownership
          bool operator() ()const{return true;}
      
        private:
          Ptr ptr_;
      };
      auto func = Functor(std::make_unique<Widget>());
      1. std::bind objects
      std::vector<int> data;
      // 1. the second argument of std::bind is std::move
      // 2. Because internally, std::bind will create a copy of all arguments, and that object will be created using its move ctor
      // 3. That object is an lvalue inside std::bind, so use const std::vector<int>&, or std::vector<int>&
      auto func = std::bind([](const std::vector<int>& data){......}, std::move(data));
  6. C++14 has "generic lambda" (I think this is the only place you can see auto as an argument)

    • Basics
      auto some_func = [&](auto a, auto b){};     //valid
      auto some_func = [](auto a, int b){};       //params don't have to be all auto
    • Internally, some_func becomes
      class CompilerGeneratedLambda{
      template<typename T, typename B>
      auto operator()(T a, B b) const{
        }
      // Don't forget the const, by default operator () of lambdas are const functions
      }
    • Universal reference
      auto some_func1 = [](int&& x){};
      auto some_func2 = [](auto&& x){};
      int i = 1;
      some_func1(i);    //Error, needs rvalue reference
      some_func2(i);    //Fine
    • Variadic Params
      // for auto&& and iny&&, many others
      auto some_func1 = [](auto&& ...args){
      return f(std::forward<decltype(args)>(args)...);
      };
      // for auto&& only
      auto some_func2 = [](auto&& ... args){
      return f(decltype(args)(args));     //?
      }
  7. Lambda vs std::bind

    • Lambda is more concise (see cautions in std::bind )
      • For example, you need a lot of bind to make sure args were evaluated when they're called, not when they they're passed in.
    • You need type casting to disambiguate overloaded functions
    • Functions inside a Lambda is inlined in its closure class, while in std::bind, they're function ptrs, which might be slower
    • std::bind by default creates copies to params passed by it
  8. lambda doesn't need to capture static member variables

    class Foo{
        public: 
            static void foo(){
                auto func = []{f = 666;}; 
                func();
            }
        static int f; 
    };
    
    int Foo::f(999); 

Cautions:

  1. Pass by reference: You gotta make sure the reference will live longer than the closure
      std::vector<std::function<int(int)>>;
      void addDivisor(){
        int divisor = 32;
        filters.emplace_back([&](int value){return value/divisor;});
      }   // Error: divisor lives shorter than the closure
    • So make sure you use the closure right after the references
    • Good practice: write out all the references.
      std::all_of(vec.begin(), vec.end(), [](int i){return i > 0;});
      std::all_of(vec.begin(), vec.end(), [](const auto& i){return i > 0;})
  2. Pass by value:
    1. You should not copy raw pointer, use a unique ptr. Otherwise, there might be double freeing/dangling ptr
    2. static/global variables are accessible to any lambdas
      • static/global variables are known during compile time, so their memory is known and won't change
      • pass by reference/copy means "to put the reference/copy into the function".
        • So there's no need to put a reference/copy to static/global variables
      • So pass by value is not immuned to accessing static variables
        void addDivisor(){
            static int divisor
            std::vector<std::function<int(int)>> lambda_vec;
            lambda_vec.emplace_back(){
                [=](int value){return value/divisor;}
              }
            ++divisor;    // You think you've passed a copy of divisor? Wrong!
          }
    3. You cannot copy a member variable directly
        class Foo{
          int divisor;
          void addDivisor(){
              std::vector<std::function<int(int)>> lambda_vec;
              lambda_vec.emplace_back(){
                  [divisor](int i){return value/divisor;}   //ERROR: divisor is not a local variable
                  [=](int i){return value/divisor;}   //This works, because = will copy this pointer, then divisor is implicitly
                  expanded as this -> divisor
                }
            }
        };
      • but even using = can possibly put a dangling pointer into the vector.
            std::vector<std::function<int(int)>> lambda_vec;
            int main(){
              Foo* foo_ptr = new Foo();
              foo_ptr -> addDivisor();
              delete foo_ptr;
              //From now on, lambda_vec has dangling ptr in it!
            }
      • The simplest and safest thing to do, is to make a copy of the member variable, then push it into the function
        std::vector<std::function<int(int)>> lambda_vec;
        void Foo::addDivisor(){
          auto divisor_copy = divisor;
          lambda_vec.emplace_back(
              [divisor_copy](int value){return value/divisor;}   //ERROR: divisor is not a local variable
            });       //C++11 way
        
          lambda_vec.emplace_back(
            [divisor = divisor](int value){return value/divisor}
          );
        }
  3. Pass by value might be better than pass by reference
    • Scenario: two functions for lvalue, rvalue std::string are kinda redundant.
      • If they're inlined, that's fine, because inline functions are eventually not actual function calls in machine code
      • If they're not inlined, they will be two functions in object code, that'd be kinda redundant!!
    • Solution 1: universal reference
      template <typename T>
      class Foo{
          void Bar(T&& b);      // here T should either be std::string&& or std::string&&
      }
    • Cons:
      1. May stay only in header file because of the template
      2. When you pass in a string literal, a temp obj will be created for std::string, then you move the temp obj, and finally you destroy it.
        • So functions taking in universal reference will be more expensive with input args
      3. Perfect forwarding can fail:
        1. Passing in bitfields, cuz bits cannot be referenced. On hardware level, ref and pointer are the samething.
        2. Passing in Templated function without casting its type.
        3. 0 or NULL as they will be deduced to int instead of ptr type
          • if type deduction fails, perfect fowarding fails.
        4. Braced Initializer
          • you can explicitly create such an object using auto
        5. Specific to std::string
          • char 16_t[] cannot be directly converted to std::string
          • If char 16_t[] is passed in as an argument and perfect-forwarded to std::string, error msg is ugly
    • Solution 2: pass by value
      Foo::Bar(std::string new_name);
      Bar(std::move(some_name));
      • Pros:
        1. new_name is completely independent
        2. You can use std::move for a name that's not used any more.
      • Cons:
        1. One more move if we pass in std::move (but if this move is not expensive, we can totally use it!!)
          • the cost is: move-construct temp obj, use it just as passing in lvalue ref and rvalue ref, finally destroy it
          • cost for lvalue ref, rvalue ref: 0.
        2. Consider this only for copyable objects. Non-copyable objects will likely be movable only, which is better for rvalue-refeence + std::move
      • Cautions:
        1. You construct object by 1. ctor, 2. assignemnt =
        2. std::string move assignment (like std::vector) will steal the ptr to the underlying char[]
        3. copy ctor won't steal the ptr, instead it will copy chars.
          • the old char[] will be deallocated, if it's shorter than the new string.
        4. This can add up, so by default DO NOT USE PASS BY VALUE
        5. You might have some "slicing issues" with pass-by reference (no inheritance enabled)

========================================================================

std::function

========================================================================

std::function vs function pointer

  1. Motivation

    • std::function can store any "callable objects", (class with overloaded(), lambda,etc.), aims to replace function pointer.
  2. Use of std::function

    • std::function(void(output)) is common practice
    • std::function can be used as a left value
        #include <functional>
          int func (int) {} 
          class Bar{public: int operator()(int a){return a; }};//this is a functor
        int main()
        {
          Bar bar; 
          std::function<int(int)> foo = func;  
          foo = bar;
        }
    • std::function can be used to represent static, non static functions, after binding.
      int(int) func; 
      class Lol{
        static int (int a) static_lol; 
        int (int a) non_static_lol; 
      }
      
      int main(){
        std::function<int(int)> foo1 = func
        std::function(int<int>) foo3 = Lol::static_lol; 
        Lol lol; 
        std::function<int(int)> foo2 = std::bind(&Lol::non_static_lol, &lol, std::placeholders::_1);
      
        func1(1)
      }
    • const std::function just means this object doesn't point to other objs. std::function <void const...> is actually pointing to a const function.
  3. Drawback of std::function

    • throws an exception.
    • std::function is slower, and takes up more space, because it the object itself takes up fixed amount of memory on stack. If memory is not enough, it will go to heap
  4. std::function requires the callable to be copy constructible

    • non-copy constructible functions can be a move-only lambda, which can be stored in std::unique_function
    • This can happen when you have a lambda that captures a std::promise, std::promise is not copyable, so this lambda is not copy constructible.
    • see this nice article
  5. check if std::function has a valid function

    void checkFunc( std::function<void()> &func )
    {
        // Use operator bool to determine if callable target is available.
        if( func )  
        {
        }
    }
    
    // or 
    if (func != nullptr){
    }

========================================================================

Function pointer

========================================================================

  1. Function pointer vs std::function

    1. function pointer is 16 bytes, while a regular pointer is only 8 bytes.
    2. Function pointer points to data, not to code, so we don't need to free that memory
    3. Return type of a function pointer should have *
      • Also, if we remove (), it will become a function that returns void*.
        void (*func_ptr)(double) = &some_void_func;
        void (*func_ptr)(double) = some_void_func;    //the function name = the function's address
    4. There's also a simpler version of function pointer
      int pf(int);
    1. Invoking a function thru function pointer:
      (*func_ptr)(10); 
      func_ptr(10); //the function name = the function's address
  2. pointer to member function vs functor vs std::function:

    1. function pointer to member function and const member function
    class Foo{
      bool f1(int i); 
      void func() const {printf("hello");}
      }
    
    // one: pointer to const member function
    using int (Foo::*func)(int) const = &Foo::func;     //of course to call it properly you have to do std::bind(foo_func, &foo_obj)
    
    int main(){
      bool (Foo:*mfn)(int); 
      mfn = &Foo:f1;    //in c++, member functions belong to the class, not a specific object. 
      Foo foo; 
      (foo.*mfn)(11);   //Pay attention to the syntax here!!
      }
    1. Functor for member function, using std::mem_fn, which generates a wrapper that can call a member function of the parent object.
    #include <functional>
    struct Foo{
      void foo(int i){}
    }; 
    
    int main(){
      Foo f; 
      auto foo_wrapper = std::mem_fn(&Foo::foo); 
      foo_wrapper(f, 20); 
    }
    • Cons: you have to know the exact type of the object. If the object type is different, even if your function is of the same type, you're done.
    1. USE an std::function instead
    using function_type = std::function<double(int, float, double)>;
    std::vector<function_type> bindings;
    
    // var #1 - you can use simple function
    function_type var1 = foo_fn;
    bindings.push_back(var1);
    
    // var #2 - you can use member function 
    function_type var2 = std::bind(&foo_struct::foo_fn, fs, _1, _2, _3);
    bindings.push_back(var2);

========================================================================

Std::bind

========================================================================

  1. generate a function object

    • succeeds std::bind1st, std::bind2nd
    • IF you can use lambda, don't use std::bind
  2. bind can be used on: function object, a pointer to function, or even a pointer to member, and will return an object of the same type.

    #include <functional>
      int func1(int){} 
    
      class Lol{
      public:
          int l; 
          void non_static_lol(int i){cout<<i<<endl;}; 
      };
        
        
      int main()
      {
          //bind to function object
        std::bind(func1, std::placeholders::_1);
    
      // bind to a member function
        Lol lol; 
        auto func2 = std::bind(&Lol::non_static_lol, &lol, std::placeholders::_1);	//Note that the first parameter should be an object for the class, so you get x.non_static_lol(); ALSO IMPORTANT: don't use object->func for bind!!
        func2(4);		
      }
    • std::bind(&func, 1) always passes 1 into the function.
    • std::placeholders::_1, _2 ..., these are the Nth argument.
  3. Nice Usage

    • passes in func1 as an argument for Foo, and func1 doesn't have to be evaluated when being passed in, but when Foo is invoked
      Foo(int); 
      int func1(return CurrentTime);  //currenttime in seconds
      //without std::bind
      Foo(func1());   //CurrentTime is evaluated when being passed in
      
      //std::bind
      auto Foo_bound = std::bind(Foo, std::bind(func1));   //You put func1 there, but not evaluated yet. 
      Foo_bound();        // now currenttime is being passed in. 
  4. Cautions

    1. Internally, std::bind will create a copy of all arguments. Therefore, move/copy ctor might be invoked
      Foo f; 
      int i = 3; 
      std::bind(func1, std::move(i));     // move ctor is used inside std::bind to create a copy of the param
      std::bind(func1, i);     // copy ctor is used internally
    2. VERY IMPORTANT, you should use std::ref to bind the function from the same object, otherwise, copy ctor is called by std::bind!!!. std::ref is also in . By default, std::bind has const operator() for its params
      std::bind(&Foo::func, std::ref(obj));
    - **You need type casting to disambiguate overloaded functions** 
         ```cpp
         void Foo(Time t); 
         void Foo(Time t, double i); 
         auto Bar = std::bind(Foo, std::bind(std::plus<>(), std::chrono::steady_clock::now(), 1h), std::placeholders::_1);     // ERROR: std::bind doesn't resolve overloaded functions, so there's ambiguity here
         auto Bar = std::bind(static_cast<void(*)(Time, double)>(Foo), std::bind(std::plus<>(), std::chrono::steady_clock::now(), 1h), std::placeholders::_1);     // std::bind doesn't resolve overloaded functions, so there's ambiguity here
         ```
    
    1. std::bind internally has a function pointer that calls the bound function, which might be slower than lambda (inline function)
    Bar(123.4);      // calls the function to Foo 
    // Lambda
    auto Bar_lambda = [](double i){Foo(i);};    //Foo is inline, which is faster
    1. Bind to class member:
      Foo::foo(){
      std::bind(&Foo::bar, this, std::placeholders::_1);
      }
      
      void Foo::bar(int){
      }
    2. Note: this pointer must have been constructed, so you can't do this in initializer.
Clone this wiki locally