C++17 Tips

5 minute read

Overview

I will show in this article some tips and tricks using the new C++17. For the tests I used Clang version 5.0 and GCC 7.2 maybe in previous versions will not work. C++17 is more intuitive and definitely a more modern and productive programming language.

Using aggregates

  • Now it is possible to leave a public aggregate of a base class.
  • Initialization is possible with {} key.

See example:

#include <cstdint>

struct CType 
{
  uint32_t value; 
};

// C++17.
struct Derived : CType 
{
  int status;
  void f();
};

int main(void)
{
  // C++17 Init.
  Derived d{ {42}, 1 };

  // C++17 Init.
  Derived e{ {}, 0};
 
  return 0;
}

To compile the example:

clang++-devel -std=c++1z aggregate.cpp -o aggregate && ./aggregate

Run on GCC 7 using Compiler Explorer: aggregate

New assert format

In static_assert it is no longer necessary to pass the error message as parameter, the automatic message already exists. If an assert violation occurs at the time of compilation the error is already automatically captured.

See example:

int main(void)
{
  // C++17.
  static_assert(sizeof(int) >= 4);

  // C++17.
  static_assert(2 + 2 == 4);

  return 0;
}

To compile the example:

clang++-devel -std=c++1z assert.cpp -o assert && ./assert

Run on GCC 7 using Compiler Explorer: assert

Using std::initializer_list and std::forward

You can apply this function to search for arguments:

See example:

#include <initializer_list>
#include <utility>
#include <iostream>

template <class F, class ... Ts>
void for_each_argument(F f, Ts&&... a) {
  (void)std::initializer_list<int>{(f(std::forward<Ts>(a)), 0)...};
}

int main(void)
{
  for_each_argument([](const auto &arg){ std::cout << arg << "\n"; }, 1, 2, 3, 4, 5);
  return 0;
}

To compile the example:

clang++-devel -std=c++1z for_each_argument.cpp -o for_each_argument && ./for_each_argument

Run on GCC 7 using Compiler Explorer: for_each_argument

Static inline variable

Static variables inline, no longer need to be initialized in the source file, can initialized directly into the class.

See example:

// class_global_static.hpp
#include <iostream>

class Log 
{
private:
  static inline bool m_log = false;

public:
  static void setEnabled(bool log = false) {
    m_log = log;
  }

  Log() {
    if (m_log)
      std::cout << "Hi! log is enabled.\n";
  }   
};

int main(void)
{
  Log::setEnabled(true);

  Log lg;

  return 0;
}

To compile the example:

clang++-devel -std=c++1z class_global_static.cpp -o class_global_static && ./class_global_static 

Run on GCC 7 using Compiler Explorer: class_global_static

Lambda function.

Using lambda to catch this class.

See example:

#include <iostream>
#include <string>

class A {
private:
  std::string m_name;

public:
  void test() {
    // C++17 create local copy from this.
    [*this] { std::cout << m_name << std::endl; };

    [this] { std::cout << m_name << std::endl; };
    [=] { std::cout << m_name << std::endl; };
    [&] { std::cout << m_name << std::endl; };
  }
};

int main(void)
{
  A a;

  a.test();

  return 0;
}

To compile the example:

clang++-devel -std=c++1z lambda_this.cpp -o lambda_this && ./lambda_this

Run on GCC 7 using Compiler Explorer: lambda_this.cpp

Portability with __has_include

The __has_include function can be used to test whether an include exists and also helps with application portability.

See example:

#if __has_include(<optional>)
#include <optional>
#define DIAGNOSTIC_TYPE = 1
#endif __has_include(<proto>)
#include <proto>
#define DIAGNOSTIC_TYPE = 2
#else
#define DIAGNOSTIC_TYPE = 3
#endif

Namespace.

With C++17 you can initialize the namespace directly.

See example:

#include <iostream>

// C++98.
namespace A {
  namespace B {
    namespace C {
    }
  }
}

// C++17.
namespace A::B::C {
static void hello() {
  std::cout << "Hello, C++17 namespace\n";
}
}

int main(void)
{
  A::B::C::hello();

  return 0;
}

To compile the example:

clang++-devel -std=c++1z namespace.cpp -o namespace && ./namespace

Run on GCC 7 using Compiler Explorer: namespace.cpp

Structured Bindings

Introduced under proposal P0144R0, Structured Bindings give us the ability to declare multiple variables initialised from a tuple or struct.

See example:

#include <iostream>
#include <tuple>

int main(void)
{
    auto tuple = std::make_tuple(1, 'A', 2.3);

    auto& [a, b, c] = tuple;

    std::cout << "a=" << a << ", b=" << b << ", c=" << c << '\n';

    a = 2;

    std::cout << std::get<0>(tuple) << '\n';

    return 0;
}

To compile the example:

clang++-devel -std=c++1z tuple.cpp -o tuple && ./tuple

Run on GCC 7 using Compiler Explorer: tuple.cpp

Templates class arguments

Using template class with arguments and initializing.

See example:

#include <iostream>

template <typename T1, typename T2>
class Test {
public:
  explicit Test (T1 x = T1(), T2 y = T2())
  {
    std::cout << "X=" << x << " Y=" << y << "\n";
  }
};

int main(void)
{
  // C++98.
  Test<int, double> a1; 

  // C++17
  Test a("Hello", 10);
}

To compile the example:

clang++-devel -std=c++1z template.cpp -o template && ./template

Run on GCC 7 using Compiler Explorer: template.cpp