Some cool features of C++ 14 and C++ 17

Some cool features of C++ 14 and C++ 17

Table of contents

C++ 14

  • Variable Templates- C++14 allows defining a variable template, which is similar to a function template but defines a variable instead of a function. Variable templates can be used to define a family of variables with different types, but the same value or behaviour. For example:

      #include <iostream>
      using namespace std;
      template<typename T>
      constexpr T pi = T(3.1415926535897932385);
      int main() {
        float x = pi<float>;   
        int y = pi<int>;
    
        cout << "x = " << x << endl;
        cout << "y = " << y << endl;
      }
    
      OUTPUT-
      x = 3.14159
      y = 3
    
  • Return Type Deduction for Functions- C++14 allows deducing the return type of a function based on the type of its return statement. This can simplify the syntax of functions that return complex types, such as lambdas or templates. For example:

      #include <iostream>
      using namespace std;
      // The return type is 'int' but it is auto deduced by compiler using auto keyword.
      auto divide(int x, double y) {
        if (y == 0)
          return static_cast<int>(0);
        else
          return x / static_cast<int>(y);
      }
      int main() {
        auto x = divide(10, 2.2);
        cout << "x = " << x;
      }
    
      OUTPUT- 
      x = 5
    
  • Aggregate Initialization- C++14 allows using brace initialization to initialize aggregate types, such as arrays, structures, and classes. This makes it easier and more consistent to initialize complex types. For example:

      #include <iostream>
      using namespace std;
      struct Point { int x, y; };
    
      int main() {
        // brace initialization 
        Point p1 = { 1, 2 };
    
        cout << p1.x << endl;
        cout << p1.y << endl;
      }
    
      OUTPUT-
      1
      2
    
  • constexpr Functions- C++14 extends the use of constexpr functions, which are functions that can be evaluated at compile time allowing their result to be used in constant expressions, such as array sizes or template arguments. This can improve the performance and efficiency of the code by avoiding runtime calculations. For example:

      #include <iostream>
      using namespace std;
      constexpr int square(int x) { return x * x; }
    
      int main() {
        //calculating array size during compile time
        int a[square(5)];
        a[24] = 80;
        cout << a[24] << endl;
    
        // evaluates at compile time
        constexpr int b = square(3);
        cout << b;
      }
    
      OUTPUT-
      80
      9
    
  • Binary Literals & Digit separation- Binary literals provide a convenient way to represent a base-2 number. It is also possible to separate digits with '.
    For example:

      #include <iostream>
      using namespace std;
    
      int main() {
        // binary literal using '0b'
        int x = 0b110; 
        // digit separation using ' for better readability
        int y = 0b1111'1111;
        cout << "x= " << x << " & " << "y= " << y;
        return 0;
      }
    
      OUTPUT-
      x= 6 & y= 255
    

C++ 17

  • Structured bindings- Structured bindings allow decomposing tuples, arrays, and other types into individual variables in a concise and readable way. For example:

      #include <iostream>
      using namespace std;
    
      int main() {
          int arr[3] = { 1, 2, 3 };
          // Here x,y,z are bound to the array elements using structured binding
          auto[x, y, z] = arr;
          cout << x << " " << y << " " << z << endl;
          return 0;
      }
    
      OUTPUT-
      1 2 3
    
  • if and switch with initializer- if and switch statements with initializer allow the use of an initializer, similar to the for loop, providing a more concise and readable syntax. For example:

      #include <iostream>
      using namespace std;
    
      auto get_value1() {return 5;}
      auto get_value2() {return -4;}
      int main() {
       if (auto value = get_value1(); value > 0) 
          cout << "Value is positive: " << value << endl;
    
        switch (auto value = get_value2(); value) {
          case 0:
              std::cout << "Value is zero" << std::endl;
              break;
          default:
              std::cout << "Value is not zero: " << value << std::endl;
              break;
        }
      }
    
      OUTPUT-
      Value is positive: 5
      Value is not zero: -4
    
  • Nested namespaces- Nested namespaces can also be useful for organizing code into logical groups and improving code readability. By grouping related functions and variables inside a nested namespace, you can make it clearer which parts of the code are related to each other and what their purpose is. For example:

      #include <iostream>
      namespace outer {
        namespace inner {
          void foo() {
            std::cout << "Hello from inner namespace!\n";
          }
        }
      }
    
      int main() {
        outer::inner::foo();  // Hello from inner namespace!
        return 0;
      }
    
      OUTPUT-
      Hello from inner namespace!
    
  • Direct list initialization of enums- This allows you to initialize an enumeration value directly from a list of values, rather than using a cast or an explicit constructor call. This feature can make your code more readable and can help to prevent errors caused by incorrect casting or constructor calls. For example:

      #include <iostream>
      using namespace std;
      enum Color { RED, GREEN, BLUE };
    
      int main() {
        Color c1{Color::RED};  // Direct list initialization
        Color c2 = Color::GREEN;  // Copy initialization using an explicit constructor call
        cout << c1 << endl;
        cout << c2 << endl;
        return 0;
      }
    
      OUTPUT-
      0
      1
    
  • constexpr if- constexpr if allows conditional compilation based on a compile-time condition, enabling more expressive and efficient code. The feature allows you to discard branches of an if statement at compile-time based on a constant expression condition. For example:

      #include <iostream>
      using namespace std;
      template <typename T>
      void print(T value)
      {
          if constexpr(is_pointer<T>()) {
              cout << "Pointer value: " << value << endl;
          } else {
              cout << "Non-pointer value: " << value << endl;
          }
      }
      int main() {
        int x = 5;
        int* p = &x;
        print(p);
        print(x);
        return 0;
      }
    
      OUTPUT-
      Pointer value: 0x7ffdf8c...
      Non-pointer value: 5
    

There are more new features in C++ 14 and C++ 17 versions, but these are the most used and important ones.
There is also a newer version which is C++ 20, which has several important and major additions to its features.