Compiler Warnings

  1. Something I don’t know about: Wpedantic disables language extension
    1. which is good

Range-based for loop

  1. const auto & in most cases
  2. auto & if you need to modify the element inside the container
  3. auto && if moving elements out of the container


  1. Don’t use Type T for template. Use some meaningful names (like BinaryPred, UnaryPred)


  1. In most cases, delete 4 special members (except constructor, destructor) when you use inheritance because it’s hard to define a correct copy/move constructor and assignment
    1. If there is no slicing issue, you could try default them

Undefined Behavior

  1. Compilers are allowed to assume no undefined behavior happens in your code
    1. So, sometimes, your code will be optimized out because of this reason
    2. In case this happens, it really means you are relying on UB
  2. Checking reference == nullptr is undefined behavior
  3. Dereferencing nullptr is also undefined behavior


  1. Don’t use default in switch
    1. We are talking about using switch with enum
    2. This rule applies to C
    3. Because if we add new options, default will disable us to get warning about the missing cases

const everywhere

  1. const as much as you can as soon as it doesn’t affect implicit move

  2. If you need to modify code, you can use immediately-invoked lambda 1

    const auto data = [] () {
        std::vector<int> result;
        // modification here
        // NRVO (very likely)
        return result;

Exception Handling 2

  1. Lippincott exception handling
    1. A centralized way to deal with exception
  2. Basically, catch all the exception with catch(...)
    1. Inside the catch block, call lippincott
  3. This lippincott function tries to rethrow the error with throw; inside a try block with many possible catches
    1. If any of them matches, we process that particular exception
    2. If this function is called without any exception, std::terminate is called

Make your interface hard to use incorrectly

Effective C++ item 18: Make interfaces easy to use correctly and hard to use incorrectly

  1. Even though your data might be easy (just a int or bool or …), it’s still necessary for you to create a wrapper for it to tell people explicitly what’s the meaning of the code.
    1. Then, it’s hard for people to use your interface incorrectly
  2. Any function or overload can be =deleted in C++11
    1. This disables people pass parameters that could implicitly convert to our specified types unexpectedly. For example, if we want to accept double and reject float, we delete the overload for float.

Don’t Use initializer_list For Non-Trivial Types

Talk: C++Now 2018: Jason Turner “Initializer Lists Are Broken, Let’s Fix Them”

Slide: initializer_lists Are Broken, Let’s Fix Them

  1. std::initializer_lists<T> is like a view instead of a concrete container that actually controls the lifetime of the object

    1. It’s a pointer to an local array
    2. “An object of type std::initializer_list<E> is constructed from an initializer list as if the implementation generated and materialized a prvalue of type “array of N const E”, where N is the number of elements in the initializer list. Each element of that array is copy initialized with the corresponding element of the initializer list, and the std::initializer_list<E> object is constructed to refer to that array”
    3. So, returning an initializer_list from a function is like returning a pointer to a local object
  2. Because the generated array is const, we can only use copy constructor when using initializer_list

    1. This causes us fail to construct a vector of unique_ptr via initializer_list

    2. But, we can still construct an array via initializer list because this is aggregate initialization instead of initializer_list

      struct X {
          X(std::initializer_list<double> v);
      X x{ 1,2,3 };
      // is equivalent to
      const double __a[3] = {double{1}, double{2}, double{3}};
      X x(std::initializer_list<double>(__a, __a+3));
    3. No narrowing conversions allowed because we are initializing the member via {}

  3. Possible Improvements

    1. A non-const array with movable iterator
      1. check std::make_move_iterator
    2. Construct in-place by inheriting from vector
  4. Interesting Observations

    1. Compilers may not have the ability to track the size of const char *. To have a higher probability of doing SSO, construct the string directly when trying to return const char * from functions and later try to construct a string with this const char *
  5. std::in_place_t

    1. A tag used by variant, optional and any so that we can default construct the actual object in-place. These types could be non-movable/non-copyable.
    2. std::make_optional, std::make_any, std::in_place (the global value that has type std::in_place_t), std::in_place_type<T> (useful when trying to decide which type in variant to init), std::in_place_index<val> (specify which index to create/set)
    3. See this article for more details

Extern Template 3

  1. If we use one template with a specific type frequently, extern template can help us solve the time to instantiate the template in each translational unit
    1. In the normal process, template is initialized in every translational unit if it’s used and during link time, only one instantiation will be kept.
  2. By using extern template void Fun<T>();, we signify that we have a template already instantiated.
    1. Put this declaration in header file to make sure when we compile this code with -O3, the instantiated template is not inlined.

  1. You can check Jason Turner’s channel C++ Weekly - Ep 70 - C++ IIFE in for more information about IIFE (immediately-invoked function expression) ↩︎

  2. “Using a Lippincott Function for Centralized Exception Handling” ↩︎

  3. “Reduce Compilation Times With extern template” ↩︎