C _Struct_Oop_Template - RicoJia/notes GitHub Wiki

OOP

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

Members

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

Regular Members

  1. const member functions

    • it can only call other const member functions
    • e.g
      const T& operator[] const(size_type i) {return array[i];}
      • 第一个const 就是返回一个const T& object,
      • 第二个const 是在执行过程中对objectread only access. 如果没有第一个const,他并不会在compile 中出现问题,但是这会在使用中出现问题。若无第二个const,便无法保证他object不会被改变。那么compile的时候就会出现问题!
      • mutable 可能是一个很好的工具,他可以让这个指针本身会变,即使你用const template function.如果你有const member functions 的话,有的member的子variable可能不让你 access。 这个时候你应该加上mutable。
      • 或者用const_cast
    • const member variable: must be initialized in ctor.
  2. deleted functions generally are declared as public, to generate better error messages.

  3. something& cannot be a class member? yes,but you have to declare and initialize them at the same time.

    • about std::function as a member variable: Just use std::function, & or const std::function& are error prone

Static Member functions, variables

  1. Access Specifiers:
    1. static members can be accessed without any objects
    2. non-static functions can access static members. no need for class name!
    3. static member function:
      • 用作整个class 的通用function, 这个function 只和class 相关,和object 无关。 他的目的仅仅是access static member variable, 无法access 其他variables
      • 他是 class::static_func, 所以他可以避免overloading, 进而可以拿class:static_func来作为template argument
    4. static member variable: static members must be defined outside of the class, in the same namescope using ::
      class Foo{
          public: 
              // This is declaration, see below for definition
              static int i; 
              // can declare static func inside class
              static void foo_static(){
                  cout<<__FUNCTION__<<endl;
              }
              // calls static, if in the same class, no need to add class name
              void foo(){
                  foo_static();
                  Foo::foo_static();
              }
      };
      
      class Child:public Foo{
          public:
              void foo(){
                  // even child class can call the base class static func
                  foo_static();
              }
      }; 
      
      // This is how to initialize static member func, this is definition
      // If in hpp, then every instance will get the same value. Else, should be in the source file.
      int Foo::i(1); 
      
      int main()
      {
          Foo f;
          f.foo(); 
          cout<<Foo::i<<endl; 
          Foo::foo_static();
      
          Child c; 
          c.foo();
          return 0;
      }

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

Accessibility

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

Basic

Who can access what?

  1. friend functions: can access private members

    • 【习惯】friend function 不需要Public 或private。 所以你可以把所有friend function 放到class definition 最上面,接近public位置。
       class foo{
       	friend std::istream& operator>>(std::istream&, Str&)
       	public:
       	...
       }
      • 这个friend function 和member function 是同等privilege
      • 有时,你不用friend function:
        • Can I add std::istream & operator>>(std::istream & is, Transform2D & tf); as a friend function to the Transform2D? This way I can modify private member Matrix's values? A: you don't need this
  2. Friend Class - can access friend funcs too

    • e.g, student_handle 列为core 的friend class, 这样all member functions in student_handle will have access to core's private, protected, and public members (program verified).
      class Core{
              friend class Student_Handle; 
          }
    • friend class 无法传递: 即parent 的friend 不是child friend (所以data member 你都获得不了, 不过你可以根据parent 的virtual function 来接触child class); friend 的friend 不可以传递哦。
    • Friend class is not mutual.
  3. Inner class: enclosed class is actually a friend class, so it can access private members as well

    class Enclosing {
        private:
           int x = 30;
        public:
           class Nested {
               public:
                  void nested_func(Enclosing *e) {
                    cout<<e->x;  // works fine: nested class can access
                                 // private members of Enclosing class, if placed in
                                 // either private or public
                  }
           }; 
           Nested n_;   // Nested must have been declared
           void func(){
               n_.nested_func(this); 
           }
    }; // declaration Enclosing class ends here

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

Constructors

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

High level summary

  1. Compiler will generate funcs that are public, inline, non virtual (except virtual destructor for a child class) :

    1. default ctor (if there's no other ctor), and the big five
    2. default destructor
    3. default copy operations:
      • ctor
      • copy assignment, =
    4. default move operations (C++11), Each performs memberwise copy and assignment, as well as base-class parts. If no support for move, then copy
      • ctor
      • move assignment, =
  2. ctor's initializer doesn't have a complete this pointer yet.

  3. Rules of three, five, zero

    • "Rule of three" means: if one of copy ctor, =, destructor is declared, then others should be declared as well. same reasoning as move operations.
      • STL containers with dynamic memory management all have the three implemented.
    • Rule of Five just includes the move ctor and =
    • Rule of zero means if you don't touch Any of the above, don't declare anything. If you've touched any of them, follow the previous rules.
  4. Initializations

    • direct initialziation uses () or {} obj(args); or obj{args}
    • copy initialization uses = for contruction.
  5. Declare something as default, if the default memberwise behaviour satisfies your need:

      class Foo{
          Foo() = default; 
          ~Foo() = default; 
          Foo(const Foo&) = default;  //copy ctor
          Foo& operator= (const Foo&) = default;//= 
          Foo(Foo&& ) = default;      //move copy ctor
          Foo& operator= (Foo&&) = default;    //move =
        }
  6. templated copy ctor, = has no effect on the five, yayy

      class Foo{
      //copy ctor from anything, even though it could be default constructor, but you can still do rule of zero  
      template <typename T>
      Foo(const T& rhs);    
        }

What Ctors are synthesized

  1. Will automatically synthesize all three even if you've had some of them. article

    You provide \ synthesize Dtor Copy assignment Copy ctor Move ctor Move assignment
    dtor v v x x
    Copy= v v x x
    Copy ctor v v x x
    1. Dtor, copy, copy =, technically rule of 3 but not enforced, c++98 2. Exception: can't synthesize operator = (copy =) , reference member in the class (have to be initialized).
      1. Const member (have to be initialized first)
  2. Also, if you have some of the above, "there're complex resource management, no move ctor, move assignment",

    1. rule of 5, but to keep backward compatibility with c++98, only move and move = are enforced
  3. If you have move ctor, move = , then it can't be c++98, so rule of 5 holds

    You provide \ synthesize Dtor Copy assignment Copy ctor Move ctor Move assignment
    Move ctor x x x x x
    Move assignment x x x x x
  4. No default ctor in the following cases: "maybe the developer doesn't want a default ctor"

    Copy = (weird)
    Move
    Direct ctor, Class(int x, double y)

Conversion and Explicit

  1. Conversion Ctor(ctor can take 1 arg) and Explicit (doesn't allow implicit conversion, but can still be casted explicitly)
    class oop
    {
    public:
        // this is actually a conversion ctor. Can convert 
        oop (int i = 3, int j = 4.0): i_(i), j_(j){}
        bool operator == (const oop& rhs){ return (rhs.i_ == i_) & (rhs.j_ == j_); }
    private:
        int i_; 
        int j_;
    };
    
    class oop_explicit
    {
    public:
        explicit oop_explicit (int i = 3, int j = 4.0): i_(i), j_(j){}
        bool operator == (const oop_explicit& rhs){ return (rhs.i_ == i_) & (rhs.j_ == j_); }
    private:
        int i_; 
        int j_;
    };
    
    int main()
    {
        oop o;
        // conversion. Will not happen if ctor is explicit
        cout<<(o == 1)<<endl;
    
        oop_explicit oe; 
        // Explicit prohibits this
        // oe = 4;
    
        // you can still cast for explicit 
        oe = (oop_explicit)4;
    }
    
    • Explicit should be in front of non-copy ctor (such as direct initialization), but doesn't work with copy constructor (using =)
      class regex{
        explicit regex(char*);  // direct initialization permits this
      }
      std::regex rx = nullptr;    //won't compile, as = is copy initialization
      regexes.push_back(nullptr);     // won't compile, because vector::push_back uses copy initialization
      regexes.emplace_back(nullptr)   //compiles, since vector::emplace_back() uses direct initialization, though we might get undefined behavior

Details about Ctors

  1. Copy constructor

    • No need for copy constructor, if the default is fine. The default copy constructor will recursively iterate thru the class.
    • Copy elision happens during =, so after we create a temp object, we don't have to copy it. This is called "copy elision"
      class B { 
      public:     
        B(const char* str = "\0") //default constructor { 
            cout << "Constructor called" << endl; 
        }     
          
        // this must be a reference, else you are going to call copy constructor itself when copying for the first time.
        B(const B &b)  //copy constructor { 
            cout << "Copy constructor called" << endl; 
        }  
      }; 
       
      int main() {  
        B ob = "copy me";  // only default contructor is called for creating a temp object, but we don't copy it
        return 0; 
      } 
      • If we use "-fno-elide-constructors" for g++ compiler, then copy ctor will be called
    • when would copy assignment throw? not enough memory/the copy ctor throws one explicitly
  2. Copy Assignment Operator: This is called when the object has been initialized

    • Assignment operator is actually copying, too. Assignment operator returns a reference, so you can do chaining a = b =c
    • E.g
        class Foo{
            Foo& operator = (const Foo& f){
                if (&f = this) return *this;	//Ego check
                Foo.a = f.a;
                return *this; 
              }
          }
    • Copy vs assignment operator: if object hasn't been declared, copy is called; else, assignment is called
      class Foo{
      Foo(); 
      Foo(const Foo& f){cout<<"assignment"; }
      Test& operator= (const Foo& t){cout<<"copy";}
      }
      Foo T1, T2; 
      T1 = T2;		//calls = since T1 already exits
      Foo T3 = T2;	// calls copy constructor
    • Cautions
      1. Assignment Operator is Not defined if:
        • You have const non-static member (of course, they should be defined right on the spot, or in intializer list)
        • Your data member doesn't have an accessible copy constructor (ofc, you're copying stuff over)
        • Your base class doesn't have an accessible copy ctor
  3. move constructor and assignment

    • What is move constructors? Take the pointer to the resource, then nullify the moved-from object (x-value, in a "valid but unspecified state").

      • unique_ptr -> nullptr
      • std::thread -> empty thread, not joinable
      • std::future
      • std::string: random value...
    • move assignment reminder

      Obj& operator=(Obj&&){
        std::cout<<__FUNCTION__<<"RICO TODO "<<std::endl;
        return *this;
      }
    • when move ctor is not called:

      • Move operations rely on std::move, and if there's no support for move (no move ctor or assignment), then copy is performed
      • Some containers, such as std::vector::push_back(), std::stack::push(), have strong exception safety. So you move contructor must have noexcept, otherwise, copy ctor will be used.
    • Cautions:

      • You may need a move constructor for std::vector resizing.
      • default move ctor can be ill-formed, if one of your members is not movable
        • Store smart pointers to that instead
  4. Use another ctor (delegation)

    ctor(int i) : ctor(i, 1,2,3)    //always have the longer ctor here
    {}

Initialization

  1. good read. For more info about initialization basics, check out c++_basics

  2. When Initializer List must be used (Not to be confused with std::initializer_list)

    • list initializer is to "unify" initialization. So, it wors with ctors with different args. e.g, vector{beg, end} also works. see code
    • for const members, you need to use initializer list. Reasons is we don't need to default construct it.
    • reference members must be initialized using initializer list.
      class Foo{
          public:
          Foo(int a, int b); 
        }
      
      class Main{
          public: 
            MyClass(int a, Foo f) : x(a), Foo(a,a){} // pass in the arguments directly for const member
          const int x; 
          const Foo foo; 
        }
    • Note: carefule when const & is a member variable: it does create a temp copy as it would in function. but it will quickly get out of scope. Assigning that to an rvalue will result in dangling reference
      using Fn = std::function<bool (void)>; 
      class{
          Fn& fn1_;   // BAD: needs to be declared in initialization. You can't pass in nullptr, that's a rvalue. 
          const Fn& fn2_ = nullptr;   // BAD: dangling pointer. 
      }
  3. copy initialization:

    • Two ways to initialize const members: ctor or in place initialization. The "in place" intiailization is also called "default member initialization".
      // in place member initialization
      class Foo{
        private:
          Obj obj_;     // requires default initialization
          Obj obj2 = Obj(1,2,3) //copy ctor
          Obj obj3 = {1,2,3}    //implicit conversion from 1,2,3 to Obj, which requires ctor Obj(int, int int) NOT to have explicit, and copy ctor
          Obj obj4{1,2,3}     // list initialization, not copy initialization
      }
  4. list initalization:

    • struct with no ctor, and all public members:

      1. One form is of list initialization is "aggregate initialization". the declaration order must match that in struct. Not to be confused with designated initialization in C.
      2. when working with pointers, make_unique() doesn't support direct initialization, but new does.
        struct Foo{
          int i; 
          double j;
        };
        // note it has to be Foo{}, not Foo()
        new Foo{2, 3.0}
    • In C++, aggregate also needs:

      1. No custom ctor,
      2. No virtual functions, no base classes
      3. No protected, private static members
    • Stuff doesn't work with list initialization:

      1. [!] std::vector::emplace_back since it doesn't take lists
        std::vector<MyStruct>vec;
        vec.emplace_back(arg1, arg2);       // don't compile.
      2. [!] std::make_unique, cuz arg can be deduced as things that can cause narrowing, e.g., int, but actually char. But works with std::unique_ptr(new T{1,2}). Verified. See code
  5. Evolution of ctor

    • C++98: constructor + initializer list
    • C++ 11: in class non-static member initialization
      // since C++11:
      struct SimpleType {
        int field = 0;                        // works now!
        std::string name { "Hello World "} // alternate way with { }
      }
    • C++ 17: in class static member declaration with inline
      struct OtherType {
        static const int value = 10;
        static inline std::string className = "Hello Class";
      }

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

Overloading

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

Operator Overloading

  1. ctor compatible with 1 arg (aka conversion ctor): see the constructor section

  2. conversion operators

    class Int{
    	operator double (){return 3.0;} // This is conversion operator
        // explicit conversion
        explicit operator int*() const { return nullptr; }
    	}; 
    int main(){
        Int i; 
        double a = i;	//using conversion operator. 
        double b = static_cast<Int>(i)	//using conversion operator
    
        int* p = static_cast<int*>(x); // OK: sets p to null
        //  int* q = x; // Error: no implicit conversion, explicit conversion only
    }

Function Overloading

  1. rvalue reference, lvalue reference funcs:
    class Bar{
        public: 
            Foo& getFoo()& {cout<<"lvalue foo is returned "<<endl; return foo;}	//foo is already lvalue
    
            // cannot be overloaded with two getFoo()&& functions
            // Foo& getFoo()&& {
            //    cout<<"rvalue foo is returned "<<endl; 
            //    return foo;
            // }	
    
            // good
            Foo&& getFoo()&& {cout<<"rvalue foo is returned "<<endl; return std::move(foo);}	

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

Inheritance

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

  1. Short summary

    • You CANNOT call a function defined only in derived class through parent class ptr
    • All code is here: see code
  2. name overloading bw function and variable

    // in c
    int a;
    void a(){}
    int main(){
      a();    // error: variable cannot be used as function.
    }
    
    // in cpp
    class Foo{
      int a;
      void a(){}    // this is not allowed either
    }
    
    // case 3:
    class Foo{
      void a(){}
      void foo(){
        int a;
        this->a();    // this is allowed, because we are not relying on implicit invoking.
      }
    }
  3. Inheritance: object slicing from up-casting (references, or pointers, derive -> base)

    • Base part is built first, then the derived part
      • default base ctor is called. That's why you want to call the copy/move ctor explicitly from derived class. This makes sense because the derived class object naturally has a base part to copy
          class Foo{}; 
          class Bar : public Foo{
              Bar(const Bar& b) : Foo(b){}
              Bar(Bar&& b) : Foo(std::move(b)){}
            };
    • when passed by value, the object gets sliced off
        class Foo{
            virtual void eat(); 
          }; 
        class Bar:public Foo{
            void eat override{}; 
          }; 
      
        Eat_value(Foo f){f.eat()};  //calls Foo::eat()
        Eat_ref(const Foo& f){f.eat();}     //can call Bar::eat() or Foo::eat()
  4. template inheritance:

    • Base ctor must come before everything else
      template <typename T>
      class Foo
      {
          public:
            Foo (T t){}
          protected: 
            int num_;
      };
      class Bar : public Foo<int>{
          Bar::Bar() : Foo(1), basic_info_(){}
      }
  5. Multiple Inheritance:

    class TA : public Faculty, public Student {
    }

Polymorphism

  1. Vtable and virtual function

    • virtual allows polymorphism, which is to call child class function thru base class reference or ptr. The derived class function is called using vtable, created during compile time. The virtual function is resolved late during runtime.
      • "dynamic dispatch" is the word for achiecving function overriding (polymorphism), NOT overloading. You need the virtual keyword, otherwise, NO POLYMORPHISM IS ACHIEVED
    • Every class constructor will create a vtable, then create a vptr to point to the vtable. (For derived class, same).
    • When you call a virtual function, compiler uses base_class ptr -> find the right vptr -> access the right vtable -> find the right function for the current class.

  2. call virtual function in base: read below for some common senarios

    Class Base
    {   
        void func(){cout<<"base"<<endl;}
        virtual void Base_func(){cout<<"base_only"<<endl; }
        virtual void f(){g();};
        virtual void g(){//Do some Base related code;}
    };
    
    Class Derived : public Base
        void func(){cout<<"Derived"<<endl;}
        virtual void f(){Base::f();};
        virtual void g(){//Do some Derived related code};
    };
    
    int main()
    {
        Base *pBase = new Derived;      //**call Base::base() -> Derived::Derived()**
        pBase -> func();		// calls base func, because it's not virtual. 
        pBase -> base_only();		//calls the Base base_only, since it's virtual, it's in the vtable. 
        pBase->f();         // **call Derived::f() -> Base::f() -> Derived::g()**  
        return 0;  
    }
    • Notes:
      • If there are "this" pointer, it will be resolved as well.
      • note that Base::g() won't be called, because you can choose not to implement Derived::g() at all. Think the derived class object is completed, so if you want to call it, use Base::g()
        • There is one exception, where Base::g() will be called, that'd be during the constructor phase of Base, because then you don't have a completed Derived object yet (remember stack unwinding.)
  3. Abstract function (or pure function), abstract class

    • if an implementation of a fucntion is not known in base class declaration, then we have an abstract function, which is virtual ... = 0 in .h file.
      • An abstract class has AT LEAST one abstract function. The purpose is to serve as a base, enforce every child class to define it.
      • if an abstract class has no implementation for ANY function, and (it can have member variables), then it's called interface class
        class MyInterface
        {
        public:
            virtual ~MyInterface() {}
            virtual void Method1() = 0;
            virtual void Method2() = 0;
        };
      • we cannot create an object for an abstract class
  4. override

    • Motivation: It's so easy to make mistakes for overriding exactly the same functions!, That's why we need override keyword. Here you have a few cases.
      class Base{
        virtual void func1 const(); 
        virtual void func2 () &&; 
        virtual void func3 (int x) ; 
        void func4 (int x) ; 
      }
      class Derived : public Base{
        void func1 ();		// Not Overloading, because you need const. 
        void func2 () &;	// ref qualifiers should match as well 
        void func3 (unsigned int x) ;		//type not matching
        void func4 (int x) ;		//no virtual keyword in base
      }

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

Destructor

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

  1. To destruct object properly:

    1. Be EXTREAMLY CAREFUL about the destruction sequence: main dtor (body), members in reverse order in initializer list.
    2. but we almost never explicitly call the dtor. else, there'd be double-freeing at the end of the dtor
    3. If you have a while loop running: have something like this: So certain items may already be destructed when your main loop might be destructed, which causes crashes
        //params for destruction
        std::atomic<bool> stop_requested_;
        std::atomic<bool> run_stopped_;
      
         ~Foo() {
          // stop the main loop
          stop_requested_ = true;
          while(!run_stopped_.load());
         }
      
        void run(){
          while(!stop_request_.load()){
              ...
          }
          run_stopped_ = true;
        }
      • if you have a thread pool, destruct it after your object
      • So place thread pool as the last item in member declaration, so the task in thread pool can be terminated properly
      • Caveat: if you have another thread loop the major task loop, this will still get stuck. solution: an atomic that signals that.
  2. On some compilers, there might be no errors to call dtor several times, but this will technically lead to undefined behavior:

    Obj obj; 
    //lifetime is over here. see https://en.cppreference.com/w/cpp/language/lifetime
    obj.~Obj();   
    obj.~Obj(); 
    obj.~Obj(); 
  3. Private Destructor - will not affect constructor` - will not let main() or other non-child functions delete the object.

  4. Virtual and Pure virtual destructor

    • need a virtual dtor for ALL polymorphism

      class Base
      {
          public:
          // should be this 
          // virtual ~Base() {}
          ~Base() { cout << "Base Destructor\n"; }
      };
      
      class Derived:public Base
      {
          public:
          ~Derived() { cout<< "Derived Destructor\n"; }
      }; 
      int main()
      {
          Base* b = new Derived;     // Upcasting
          delete b;       // not deleting the derived part, because there is virtual keyword before base dtor
      }
    • Need pure virtual dtor for abstract class (same reason as virtual function). But also because we are not overriding the base class dtor, but calling it after the derived class dtor (which is different than other polymorphism). derived_class_1 dtor ... base_class dtor, we must define its body

      class Base
      {
          public:
          virtual ~Base() = 0;     // Pure Virtual Destructor
      };
      
      // Definition of Pure Virtual Destructor
      Base::~Base() 
      { 
          cout << "Base Destructor\n"; 
      } 
      
      class Derived:public Base
      {
          public:
          ~Derived() 
          { 
              cout<< "Derived Destructor"; 
          }
      }; 

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

Small Details about OOP

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

  1. Forward declatation - if your class needs another class that depends on yours (circular reference), you need to do this:

        class IAttack;
        class Mob{
            IAttack...
        }
    
        //Attack.cpp
        class Mob;
        class IAttack{
            Mob mob;
        }
    • only declared, not defined yet. It's just telling the compiler that "it will be defined soon", so ok to use for pointers, references, not for the whole object.
      // foo.hpp
      class Foo{
          public: 
            Foo() : bar(Bar()){}    //havent' seen the definition yet. 
            Bar bar_; 
      };
      
      //foo.cpp
      class Bar{}; 
      • seems like you can completely define the nested class outside of the hpp (forward-declaration). But this works only with pointers, Not concrete objects (tested)
        • deleting a pointer of incomplete type will lead to undefined behaviour.
  2. Reminders

    • Reminder: class functions are like:
      template<typename T>
      class ThreadSafeQueue{
        ThreadSafeQueue ();
      }; 
      
      template<typename T>
      ThreadSafeQueue<T>::ThreadSafeQueue(){}
    • Reminder: noexcept needs to be in both the declaration and the definition:
      class Foo{
        void f()noexcept;
      }; 
      void Foo::f() noexcept{}
    • Expensive: delete (calls destructor)

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

Template

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

  1. Basics
    • T inside <> is called template argument
    • Always define a template outside of a function (including main, ofc)
    • template argument is always determined at compile time.
      • which means it's a constexpr. So use a constexpr function for that.
  2. member functions of a template class need to be defined in .hpp
    • Rationale: when compiled, template declaration needs to be known. Also, the template needs to see functions definition during compile time. Compiler don't remember the details of another .cpp, so if defined in a .cpp, the main .cpp see either the tempate code, or Foo<int>, but not both at the same time.
  • Correct way to declare template functions:
    template <typename T>
    class Thread_Safe_Stack{
      template<typename Q>
      void push(Q&& object); 
    }; 
    
    template <typename T>
    Thread_Safe_Stack<T>::Thread_Safe_Stack(){}
    
    template <typename T>
    template <typename Q>
    void Thread_Safe_Stack<T>::push(Q&& object){
        stack_.push(std::forward<Q>(object));
    }
  1. function template

    template<class T> 
    void foo(T t);       // Function template, itself is not a function, but a "generator" that generate function definitions. 
    • templates can be used to deduce size_
       template<unsigned N>
           const_str(const char(&arr)[N]);
      • here, it's not template, we are using a non-type parameter: they can be:
        • Pointers
        • references
        • Integral ConstExpr
  2. Template Specialization When you have a primary template, but you want to have something different for a specific type, use template specialization.

    template<typename T>
    void is_voi(T input){};
    
    // explicit specialization for T = char
    template<>
    void is_voi <char> (char input){};
    
    //equivalent to above, since template arguments can be deducted from the function arguments.
    template<>
    void is_voi(char input){}
    • Cautions:
      • Specialized function template must come after the generic template.
      • Template specialization cannot be private
  3. Overloading. (TODO)

    • The loading sequence is: exact function > specialized function template > generic function template
    • Function overloading is functions with the same name but with different args (args only, return type doesn't matter). Use overloading if you only have different args to pass in.
      • Function overloading is not overriding, they're different things. Polymorphism is to have the same function in different derived class.
      • Use polymorphism when you have the same arguments.
        struct Base{
            void shadow(std::string){}
        };
        struct Derived: Base{
            use Base::shadow;
            void shadow(int){}
        };
        int main(){
            Derived derived;
            derived.shadow(std::string{});                  //Won't work unless you have
        }
  4. Variadic template: template that takes in any number of inputs, uysing ellipsis (...)

    template <typename T, typename ... T2>
    void foo(T var1, T2 ... vars){      //... is always between type and arg name
      cout<<var1<<endl; 
      foo(vars); 
    }
    print(1,2,3.15, "var");       //must have this as var 1 has been popped out     //must have this as var 1 has been popped out

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

Advanced Uses of Templates

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

  1. Template Metaprogramming (TMP)

    • Motivation: Compile time program, that can express any program. (turing complete).
    • May not be super practical, interesting tho
    • Example
        template <unsigned int n>
        struct Factorial{enum{val = val * Factorial<val - 1>::val};
        }
      
        template <>
        struct Factorial<0> { enum{val = 1}; }
      
        Factorial<8>::val;  // this returns 8!
      • when there's a new argument, compiler creates a new instance of the template
        • Factorial<8>::val is a different instance than Factorial<7>::val
      • why use enumeration ?//TODO
        • Because enum const expression types return in compile time
        • Why can you do Factorial::stuff? //not sure
  2. type_traits

    • std::is_integral
    • This is a template
      template <typename> struct is_integral
    • Usage
      #include <type_traits>
      cout<<std::is_integral<int>::value<<endl;
      cout<<std::is_integral<int&>::value<<endl;    //returns false
  3. std::is_same<class A, class B>

    • returns true if A and B are the same
    • cv qualifier (const and volatile) and reference is taken into account
  4. std::decay::type

    • removes cv qualifier and references
    • converts array and function types to pointers ??
    • Use:
      typename = std::enable_if<std::is_same<Person typename std::decay<T>::type>::value, T>::type;     //C++11
      typename = std::enable_if_t<std::is_same<Person, std::decay_t<T>>::value, T>//C++14, with std::enable_if_t (omit typename), std::decay_t (omit ::type)
  5. std::is_base_of<class A, class B>

    • returns true if B is derived from A, or A itself
  6. std::is_constructible<std::string, B>::value: constexpr true or false, if B can be used to construct std::string

    • std::enable_if
    • template
      #include <type_traits>
      template <bool cond, class T>
      struct enable_if{}
      
      template <class T>
      struct enable_if<true, T>{
          using type = T; 
        }
    • so when false is passed into cond, there's no type in struct. But because of SFINAE, i.e, errors will be ignored during template match searching, the compiler will keep going and try to find a best match
  7. Usage: When you have the exact number of template params for two different template functions

    template <class T, 
      class = typename std::enable_if<std::is_integral<T>::value, T>::type>    
      //you cannot do class std::enable_if ... here, have to be typename. This is a template argument
      // the templated function has two template args, one is T, one is the dependent type. So you can technically by pass this by do_stuff<double, float>
      // class = ... means the name of the param is optional. 
    void do_stuff(){
      cout<<"YEEHAA"<<endl;     
    }
    int main() {
      do_stuff<int>(); 
    }
    • Another example: two iterators
      template <class _InputIterator >
      vector (_InputIterator _first,
               typename std::enable_if<std::is_input_iterator<_InputIterator>::value, _InputIterator>::type _last)
      
      template <class T>
      vector (size_t size, T val)
    • Pros:
      • No obscure error msg from having the same template and passing in the wrong type
    • So if enable_if is false, there's no ::type passed in. But why no error msgs
      • Because error in function matching (resolving overloading) is fine, the compiler will keep searching for a match
      • But in the function body, a compiler error is irreversable.
  • typename vs class (TODO): typename can be used to define a type that's dependent on template arg.
    • Without typename the compiler doesn't know if this is a template type or not
    template <typename param_t>
    class Foo{
        typedef typename param_t NEWTYPE; 
      }
    
    // or
    typename some_template<T>::some_type     
  • class = TODO?

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

Typedef and Struct

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

struct

  1. The only difference between struct and class is by default, members in struct are public.
  2. You can optionally include a constructor:
    struct Foo{int x; int y}
    Foo foo = {x_val, y_val}; // pre c++11
    Foo foo{x_val, y_val};    // since c++11
    • in a struct, memories might be continguous if the members are of the same type, but don't bank on that. This is done using structural padding (TODO)
  3. in C, struct is very different
    • Definition
      typedef struct S{
          ...
      } T; 
      //equivalent to struct S{}; typedef S T; 
      void (T something) //valid, because this is in the T is in the global namespace
      void (struct S)  // valid. 
      void (S something) //not valid, because S is not valid "type", but struct S is. 
    • struct 里面是没有member function 的。

typedef

  1. define a pointer
    typedef int* int_ptr
    //or
    typedef struct lol{...}
    lol* ptr; 
⚠️ **GitHub.com Fallback** ⚠️