Version_Summary - RicoJia/notes GitHub Wiki

C++ 98

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

  1. std::map
  std::map<std::string, int> map_ = {{"foo", 191}, {"bar", 192}}; 

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

C++ 11

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

  1. auto's constraints:
  2. cannot be used for params
    cpp int add(auto x); // error: auto not allowed in function prototype
  3. cannot be used with arrays cpp auto arr[2] = {arr};
  4. decltype: very similar to typeof:
  • used to deduct if type of an expression
      auto x = 1; 
      decltype(x) z; 
  • good for type comparison
      if (std::is_same<decltype(x), int>)
  • constraints: you can't use it directly as a return type
      decltype(x+y) add(T x, U y);    //x and y are undefined when deducting decltype(x+y)
  • trailing return type with auto makes use of decltype
      template<typename T, typename U>
      auto add(T x, U y) -> decltype(x+y){
          return x + y. 
        }
    • but in c++ 14, you can use auto directly
      auto add3(T x, U y);
  1. for(auto itr : vec)
  2. extern template
  • motivation: currently, in the same translation unit, every instance of the template function will be compiled, even for the same exact type.
      //header.h
      template <typename T>
      void Foo(){}
    
      //source1.cpp
      #include "header.h"
      void Bar(){Foo<int>(); }    // will compile Foo<int> as an instance
    
      //source2.cpp
      #include "header.h"
      void Bar2(){Foo<int>(); }    // will compile another Foo<int> as an instance, which wastes time. 
  • If you know you have the same template function instance IN THE SAME OBJECT FILE (i.e, translational unit), you should use extern
     // source2.cpp 
     #include "header.h"
     extern template void Foo<int>();     // tells compiler somewhere else in the same object file has instantiated this"
     void Bar2(){
         Foo<int>(); 
       }
  • std::vector<std::vector<int>> matrix; is legal now with >>
  • using can be used on templates
    • before C++11, we have typedef, we can alias a type, from a template, but not for aliasing a template of template
        template<typename T>
        class MyType{
            T u; 
          }
      
        // Legal
        typedef MagicType<int, int> FakeDarkMagic;    //type from a template
      
        //Illegal
        template<typename T>
        typedef MyType<std::vector<T>> NewType;     //a template of template, illegal
    • in C++11, using is introduced
        template<typename T>
        using NewType = MyType<std::vector<T>> ;     //a template, illegal
  • default template params
        template <typename T = int>
        void Foo(T t){}
  1. rvalue & lvalue
  • prvalue: pure rvalue. like literals, returned temp variables, lambdas
      10;     //literals
      true; 
    • Special: string literals in class are Rvalues, in regular functions are lvalues
      class Foo{
          const char* && right = "this is rvalue"; 
          void Bar(){
            right = "still rValue"; 
          }
      };
      
      void foo(){
          const char* left = "this is lvalue"; 
        }
  • xvalue (expiring value, 将亡值), value that can be explicitly moved from
  • why const lvauel& can take rvalue? - Cuz fortran needs it
  1. initializer_list:
  #include <initializer_list>
  #include <vector>
  class MagicFoo {
  public:
      std::vector<int> vec;
      MagicFoo(std::initializer_list<int> list) {
          for (std::initializer_list<int>::iterator it = list.begin();
               it != list.end(); ++it)
              vec.push_back(*it);
      }
  };
  int main() {
      // after C++11
      MagicFoo magicFoo = {1, 2, 3, 4, 5};
  }
  1. new ctors
  • Delegate Ctors
      class Foo{
          public: 
              int i, j; 
              Foo(): i(100) {}
              //Foo(int j):j(j), Foo(){}        //Error: delegating ctor assumes Foo() initialize all 
              // members, thus it should be the only thing in the initializer list. A general rule of thumb 
              // is to fully define the longest ctor, then delegate other ctors to this ctor
              
      }; 
    
      int main(){
          Foo(12); 
      }
  • Inheriting ctor: before C++11, derived class will have to copy all its variables to base. With this, there's no more copying
    • note: like delegate ctor, here we assume no more members need to be initialize. (cuz they might have been initialized)
        #include <iostream>
        class Base {
        public:
            int value1;
            int value2;
            Base() {
                value1 = 1;
            }
            Base(int value) : Base() { // delegate Base() constructor
                value2 = value;
            }
        };
        class Subclass : public Base {
        public:
            using Base::Base; // inheritance constructor
            int x = 100;      // IF YOU HAVE DEFINED NEW MEMBERS: you have to initialize them this way
        };
        int main() {
            Subclass s(3);
            std::cout << s.value1 << std::endl;
            std::cout << s.value2 << std::endl;
        }
  1. override and final: to prevent accidentally missing / creating an overloading function.

  2. override cpp struct Base { virtual void foo(int); }; struct Derived{ void foo(int) override{} void foo(float) override{} // illegal, nothing to override }

  3. final ```cpp struct Base final{ // yes, final can be used here too virtual void foo() final; }; struct Derived{ void foo(){}; // illegal };

    struct Derived2 : private Base{ //illegal, cuz Base is already a "final" class

    }; ```

  4. delete and default

  5. enum class

  enum class Foo{
      foo1,       //note ,  
      foo2 = 100  // note no ;
    }; 
  if (something == Foo::foo2)
  1. lambda expression
  • params are copied when created, not when used
      void lambda_value_capture() {
          int value = 1;
          auto copy_value = [value] {
              return value;
          };
          value = 100;
          auto stored_value = copy_value();
          std::cout << "stored_value = " << stored_value << std::endl;
          // 这时, stored_value == 1, 而 value == 100.
          // 因为 copy_value 在创建时就保存了一份 value 的拷贝
      }
  • each lambda expression has its own unique closure class , but they can be implicitly converted to std::function
  • a lambda can be called like this:
      #include <iostream>
      using foo = void(int); // 定义函数类型, using 的使用见上一节中的别名语法
      void functional(foo f) { // 定义在参数列表中的函数类型 foo 被视为退化后的函数指针类型 foo*
          f(1); // 通过函数指针调用函数
      }
    
      int main() {
          auto f = [](int value) {
              std::cout << value << std::endl;
          };
          functional(f); // 传递闭包对象,隐式转换为 foo* 类型的函数指针值
          return 0;
      }
  1. shared_ptr, weak_ptr

  2. shared_ptr, reset() will reduce reference count, use_count() will see how many there are ```cpp #include #include using namespace std;

    int main() { auto ptr1 = std::make_shared(1); auto ptr2 = ptr1; cout<<ptr2.use_count()<<endl; // show reference count

    ptr2.reset();       //reduce reference count by 1
    cout<<ptr1.use_count()<<endl;       // now reference count is 1
    

    } ```

  3. weak ptr: does not involve in reference count, nothing about reference. No * and ->. - meant to solve circular reference,

    • cuz you may have shared_ptr1 -> shared_ptr2, shared_ptr2->shared_ptr1.
    • when you want to destruct the two objects owning them, you cannot. - e.g,
      #include <iostream>
      #include <memory>
      using namespace std;
    
      int main()
      {
          std::shared_ptr<int> ptr;
          std::weak_ptr<int> wk_ptr;
          {
              auto ptr1 = std::make_shared<int>(1);
              wk_ptr = ptr1;
              cout<<wk_ptr.expired()<<endl;       //0
              ptr = wk_ptr.lock();
          }
          cout<<wk_ptr.expired()<<endl;       // still 0, cuz ptr is still alive
          ptr.reset();
          cout<<wk_ptr.expired()<<endl;       // 1
    
      }

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

C++ 14

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

  1. decltype(auto), for "perfect forwarding" reference of the return type
  std::string foo(); 
  std::string& bar(); 

  decltype(auto) lookup(bool f){if (f) return foo(); else return bar(); }
  1. typename in templates (class cannot be used on dependent type name, typename can).
  • dependent type name is type name that depends on another type, like T
  • so that's why class is "old school"
      template <class A>
      void foo(const std::vector<A> vec){
          typename std::vector<T>::const_iterator it = v.begin();     // a dependent name on T
          typedef typename std::vector<T>::const_iterator iter_t; 
          iter_t p2; 
      }

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

C++ 17

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

  1. you can create a temporary variable inside an if, or switch
  if (const std::vector<int>::iterator itr = std::find(vec.begin(), vec.end(),3); itr != vec.end()){
    *itr = 4;
    } 
  1. get things directly from std::tuple
  #include <iostream>
  #include <tuple>
  std::tuple<int, double> f(){
      return std::make_tuple<int, double>(1,2.3); 
    }
  int main(){
      auto [x,y] = f(); 
    }
  1. keep the keyword "register", but with no actual meaning
  • If something is obsolete, it may still be kept
  • constexpr (see SFINAE Alternatives in types.)
  • std::is_signed_v, std::is_integral_v are std::is_signed::value and std::is_integral::value
    • std::is_signed_v (C++ 17)
        template< class T >
        inline constexpr bool is_signed_v = is_signed<T>::value
  1. optional

      #include <iostream>
      #include <optional>
      using namespace std;
      int main()
      {
          std::optional <int> o{2};
          cout<<o.value()<<endl;      // return a value here
          
          std::optional <int> j; 
          cout<<j.value_or(3)<<endl;  // if j doesn't have a value, return 3
          
          j.emplace(100);             // emplace helps avoid copy/move construction!
          cout<<j.value_or(3)<<endl;  // if j doesn't have a value, return 3
          
          return 0;
      }
    • optional: no std::optional for reference
  2. cpp 17: structural binding:

    int arr[] = {1,2,3}; 
    auto [x,y,z] = {1,2,3}; 

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

C++20 Features

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

  1. Designated Initializer
    • Definition
    struct bar{int x;};
    struct foo{int a; char c = 'a'; bar b;}; 
    foo{.a = 1};    //.a is called a designator. 
    • Wrong examples
     foo f1{.a=1, 'b'}; //mix the designator and the non-designator
     foo f2{.c = 'b', a = 1} //out of order
     foo f3{.a = 1, .a = 0};     //duplicate initialization
     foo f4{.b.x = 42; }     //nested initialization   
    • Right examples
    foo f1{.a = 1 }      //c will remain 'a', b will be {x=0;}, default initialized. 
  2. compilation: https://askubuntu.com/questions/1079885/install-g-9-for-c20
    • might need g++-8 -std=c++2a cpp_20.cpp -fconcepts
    • TODO: Exploring C++20 The Programmer’s Introduction pdf
  3. Summary
    1. : https://www.incredibuild.com/blog/what-you-need-to-do-to-move-on-to-c-20-the-complete-list
    2. https://hackaday.com/2019/07/30/c20-is-feature-complete-heres-what-changes-are-coming/
    3. https://www.modernescpp.com/index.php/thebigfour
  4. jthread: raii thread?
  5. concepts
    1. Allows the compiler to check template params during compilation.
    2. e.g
    #include <list>
    # include <algorithm>
    int main(){
        std::list<int> li = {1,2,3}; 
        std::sort(li.begin(), l.end());   // will cause error, cuz sort needs random access iterator, but list doesn't have it
      }

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

Evolution Examples

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

Variadic function and Variadic Template

  • variadic args (C)
      #include <stdarg.h>
      #include <stdio.h>
      void foo(int num, ...){
          va_list args;   // useful macro
          va_start(args, num);        // set up a pointer to the first arg on the callstack
          for(int i = 0; i < num; ++i){     // note do NOT change num, there's weird errors!
              int arg = va_arg(args, int);        // need self-promoting types such as int, or double instead of short, or char
              printf("%d\n", arg); 
          }
          va_end(args);       // gcc clang, optional 
      }
    
      int main()
      {
          foo(3, 1,2,3,4);        // program doesn't know how many args being passed in. 
      }
  • C++ 11, variadic template
    • ... has two usages:
      1. ...args means args is a parameter pack
      2. args... means to unpack the parameter pack
    • for variadic templates, you need to have at least 1 arg
    • sizeof...(), not sizeof() (C++ 11)
        template<typename... T>
        void foo(T... args) {
            std::cout << sizeof...(args) << std::endl;
        }
      
  • In variadic template, placement of ... is important
    1. unpack recursively
      template <typename First, typename... Rest> void print(const First& first, const Rest&... rest) {
          print(rest...); // recursive call using pack expansion syntax
      }
    1. ... is place differently in tempate <typename ...> and func(std::vector<typename>...vec
      // v1 is NOT a function parameter pack:
      template <typename... Types> void func1(std::vector<Types...> v1);
    
      // v2 IS a function parameter pack:
      template <typename... Types> void func2(std::vector<Types>... v2);
  • Variadic Template Function evolution
    • C++ 11: you need to have a termination function for the recursion
        #include <iostream>
        template<typename T0>
        void printf1(T0 value) {
            std::cout << value << std::endl;        // This is termination, 
        }
    
        template<typename T, typename... Ts>
        void printf1(T value, Ts... args) {
            std::cout << value << std::endl;        // printing all values but the last one, as that matches the template signature
            printf1(args...);                       // ... here is to recursively call this function, or the above function. 
        }
    
        int main() {
            printf1(1, 2, "123", 1.1);
            return 0;
        }
    • C++ 17: because you have if constexpr, which evaluates a condition during compile time, and to instantiate a template function, you need to do that in compile time!
        #include <iostream>
        template<typename T0, typename... T>
        void printf2(T0 t0, T... t) {
            std::cout << t0 << std::endl;
            // if (sizeof...(t) > 0) printf2(t...);    // not working, since if is runtime!
            if constexpr (sizeof...(t) > 0) printf2(t...);    // working.
        }
      
        int main() {
            printf2(1, 2, "123", 1.1);
            return 0;
        }
    • C++ 14 (黑魔法)
      • initializer list will ensure sequence, so some ppl will use this to expand args, too.
          #include <iostream>
          #include <vector>
          template<typename T, typename... Ts>
          auto printf3(T value, Ts... args) {     //deduced
              std::cout << value << std::endl;
              auto dummy= { (std::cout << args<<std::endl, 0)... };
          }
        
          int main() {
              printf3(1, 2, "123", 1.1);
              return 0;
          }
  • Fold Expression (C++ 17), i.e, operation and ... at the same time
    • Simple example: you need () to enclose all this!
      #include <iostream>
      template<typename ...Args>
      int sum(Args&&... args) 
      {
          std::cout<< (args + ...)<<std::endl; 
      }
    
      int main() {
          sum(1, 2, 3, 4, 5);
          return 0;
      }
  • rules:
      (E op ...) <=> (E1 op (... op (E N-1 op EN))) (unary right fold)
      (... op E) <=> (((E1 op E2) op ...) op EN) (unary left fodl)
      (E op ... op I) <=> (E1 op (... op (EN−1 op (EN op I)))) (binary left fold)
      (I op ... op E) <=> ((((I op E1) op E2) op ...) op EN) (binary right fold)
    

SFINAE Alternatives: goal - we want to know which function to call based on its argument type

  • hard to get right
  • Alternatives:
    1. tag dispatching (C++ 11)
      template <typename T>
      void func(T A, std::true_type); 
    
      template <typename T>
      void func(T A, std::false_type); 
    
      template <typename T>
      void func(T A){
          return func(A, std::is_floating_point<T>{}); 
        }
    • std::true_type are not in any library
    1. If constexpr (C++ 17)
      template <typename T>
      void func(T a){
          if constexpr(std::is_floating_point<T>{}){      //inside brackets, if the condition is met
              return; 
            }
        }
    1. Concept (C++20)
      template<class T>
      concept SignedIntegral = std::is_integral_v<T> && std::is_signed_v<T>; 
    
      template<SignedIntegral T>
      void func(T val){
    
        }

type punning - to see byte representation of int using char array.

  1. union in C++:

    1. all data members share the same data location
    2. only one member of a union can be accessed at a time
    3. accessing other data members are illegal
    4. there's no way to tell which data members are active
    5. Cannot store std::string, std::vector, reference data types, etc.
      • In C it's okay
          union pun{
            int x; 
            unsigned char c[sizeof(int)]; 
          };
        
          void bad(Pun& u)
          {
            u.x = 200; 
            cout<<u.c[0];      // the first byte 
          }
  2. in C++, reading another data member in union is undefined behavior. This is called type safety: prevents you from using one data member in another type.

    • union has bad type safety. So there's "undefined behavior"
    • So use reinterpret_cast
       int x = 200; 
       auto p = reinterpret_cast<char*>(&x); 
       cout<<p[0]; 
    • in C++ 17, use std::variant
  3. std::variant

    1. introduced in boost in 2004, implemented as an variadic template
    2. each data member is called an alternative
      • only one alternative can hold a value at a given time
      • if no alternative is not found using std::get<type>, an exception is thrown. Much better than undefined behavior
        • it will try to find the best type, if type can be transformed
        • or nullptr is returned?
      • std::variant::index() shows the index of the current active value
    3. When default-constructed, the first element gets default-constructed
      • So make sure the first element has a default ctor,
      • else, use std::monostate, for empty state
    4. Complete e.g,
      #include <iostream>
      #include <variant>
      using namespace std;
      int main()
      {
      
          std::variant<std::monostate, bool, int> var; 
          cout<<var.index()<<endl;      // see 0
          
          var = true; 
          cout<<var.index()<<endl;      // see 1
          cout<<std::get<1>(var)<<endl;       //if other alternative is accessed, then will see exception
          
          var = 100; 
          cout<<std::get<int>(var)<<endl; 
          
          cout<<std::holds_alternative<int>(var)<<endl;       // does the current variant holds int? 
          cout<<std::holds_alternative<bool>(var)<<endl;      // false, cuz now it's int
      
          if(std::get_if<int>(&var) == nullptr)               // can throw nullptr
              cout<<"Cannot get ptr to int alternative"<<endl;
          else
              cout<<"Got ptr to int alternative!"<<endl;
      }
    5. Trap:
      #include <iostream>
      #include <variant>
      #include <string>
      using namespace std;
      int main()
      {
          std::variant<std::monostate, bool, std::string> var;
          var = "holi"; 
          cout<<var.index()<<endl;        // see 1, because const char* is resolved into bool, not std::string in C++17. In c++20, it's std::string
      }
    6. Errors
      • If there's two same types, you will see no match for ‘operator=’ (operand types are ‘std::variant’ and ‘bool’)
      • If no convertible type is found, we will see no match for ‘operator=’ (operand types are ‘std::variant’ and ‘const char [4]’)
⚠️ **GitHub.com Fallback** ⚠️