

Introduction:
Today, we continue to explore the features of C++14 and C++17.

✦
New Features of C++14
✦
01
Function Return Type Deduction
✦
c++14 optimized the rules for function return type deduction:
#include
using namespace std;
auto func(int i) {
return i;
}
int main() {
cout << func(4) << endl;
return 0;
}
The above code cannot be compiled using C++11, as indicated by the compiler output; this feature is only supported in C++14.
Return type deduction can also be used in templates.
#include
using namespace std;
template<typename T> auto func(T t) {
return t;
}
int main() {
cout << func(4) << endl;
cout << func(3.4) << endl;
return 0;
}
Note:
-
If there are multiple return statements in a function, they must return the same type; otherwise, compilation fails.
-
If a return statement returns an initializer list, return type deduction will also fail.
-
If the function is a virtual function, return type deduction cannot be used.
-
Return type deduction can be used in forward declarations, but the function definition must be available in the translation unit before use.
-
Return type deduction can be used in recursive functions, but the recursive call must be preceded by at least one return statement to allow the compiler to deduce the return type.
Lambda parameter auto in C++11 required specific type declarations for lambda expressions parameters:
auto f = [] (int a) { return a; }
In C++14, this was optimized, allowing lambda expressions parameters to be directly auto:
auto f = [] (auto a) { return a; };
cout << f(1) << endl;
cout << f(2.3f) << endl;
02
Variable Templates
✦
template<class T>
constexpr T pi = T(3.1415926535897932385L);
int main() {
// 3
cout << pi<int> << endl;
// 3.14159
cout << pi<double> << endl;
return 0;
}
03
Alias Templates
✦
template<typename T, typename U>
struct A {
T t;
U u;
};
template<typename T>
using B = A<T, int>;;
int main() {
B<double> b;
b.t = 10;
b.u = 20;
cout << b.t << endl;
cout << b.u << endl;
return 0;
}
04
Restrictions on constexpr
✦
C++14 reduced some restrictions compared to C++11 regarding constexpr:
-
C++11allowed recursiveconstexprfunctions, whileC++14allows local variables and loops.
// Both C++14 and C++11
constexpr int factorial(int n) {
return n <= 1 ? 1 : (n * factorial(n - 1));
}
// Allowed in C++14, not in C++11
constexpr int factorial(int n) {
int ret = 0;
for (int i = 0; i < n; ++i) {
ret += i;
}
return ret;
}
-
In
C++11,constexprfunctions had to place everything in a singlereturnstatement, whereasconstexprinC++14has no such restriction.
// Both C++14 and C++11
constexpr int func(bool flag) {
return 0;
}
// Allowed in C++14, not in C++11
constexpr int func(bool flag) {
if (flag) return 1;
else return 0;
}
05
[[deprecated]] Attribute
✦
C++14 introduced the deprecated attribute, which can be used to mark classes, variables, functions, etc. When the marked code is used in the program, a warning is generated at compile time, notifying developers that the marked content may be discarded in the future and should not be used.
06
Binary Literals and Integer Literal Separators
✦
C++14 introduced binary literals and also introduced separators.
int a = 0b0001'0011'1010;
double b = 3.14'1234'1234'1234;
07
std::make_unique
✦
C++11 had std::make_shared, but not std::make_unique. This was improved in C++14.
struct A {};
std::unique_ptr<A> ptr = std::make_unique<A>();
08
std::shared_timed_mutex and std::shared_lock
✦
C++14 introduced std::shared_timed_mutex and std::shared_lock to implement read-write locks, ensuring multiple threads can read simultaneously, but write threads must operate independently; write operations cannot occur simultaneously with read operations.
struct ThreadSafe {
mutable std::shared_timed_mutex mutex_;
int value_;
ThreadSafe() {
value_ = 0;
}
int get() const {
std::shared_lock<std::shared_timed_mutex> lock(mutex_);
return value_;
}
void increase() {
std::unique_lock<std::shared_timed_mutex> lock(mutex_);
value_ += 1;
}
};
09
std::integer_sequence
✦
template<typename T, T... ints>
void print_sequence(std::integer_sequence<T, ints...> int_seq)
{
std::cout << "The sequence of size " << int_seq.size() << ": ";
((std::cout << ints << ' '), ...);
std::cout << '\n';
}
int main() {
print_sequence(std::integer_sequence<int, 9, 2, 5, 1, 9, 1, 6>{});
return 0;
}
10
std::exchange✦
int main() {
std::vector<int> v;
std::exchange(v, {1,2,3,4});
cout << v.size() << endl;
for (int a : v) {
cout << a << " ";
}
return 0;
}
It seems similar to std::swap, but in fact, std::exchange assigns the array {1,2,3,4} to the array v without assigning to the array v.
11
std::quoted
✦
C++14 introduced std::quoted to add double quotes to strings.
int main() {
string str = "hello world";
cout << str << endl;
cout << std::quoted(str) << endl;
return 0;
}
Output:
"hello world"
✦
New Features of C++17
✦
01
Constructor Template Argument Deduction
✦
Before C++17, creating an object of a template class required specifying the type:
std::pair<int, double> p(1, 2.2);
C++17 allows type deduction without special specification:
// c++17 auto deduction
std::pair p(1, 2.2);
// c++17
std::vector v = {1, 2, 3};
02
Structured Bindings
✦
Using structured bindings, values can be obtained from tuple, map, etc.:
std::tuple<int, double> func() {
return std::tuple(1, 2.2);
}
int main() {
// C++17
auto[i, d] = func();
cout << i << endl;
cout << d << endl;
}
// -------------***-------------//
void f() {
map<int, string> m = {
{0, "a"},
{1, "b"},
};
for (const auto &[i, s] : m) {
cout << i << " " << s << endl;
}
}
int main() {
std::pair a(1, 2.3f);
auto[i, f] = a;
cout << i << endl; // 1
cout << f << endl; // 2.3f
return 0;
}
Structured bindings can also change the values of objects by using references:
// Modify object values through structured bindings
int main() {
std::pair a(1, 2.3f);
auto&& [i, f] = a;
i = 2;
// 2
cout << a.first << endl;
}
Note: Structured bindings cannot be applied to constexpr.
// compile error, C++20 can
constexpr auto[x, y] = std::pair(1, 2.3f);
Structured bindings can bind not only pair and tuple, but also arrays and structures.
int array[3] = {1, 2, 3};
auto [a, b, c] = array;
cout << a << " " << b << " " << c << endl;
// Note that the struct members must be public
struct Point {
int x;
int y;
};
Point func() {
return {1, 2};
}
const auto [x, y] = func();
03
if-switch Statement Initialization
✦
After C++17, it can be done like this:
if (init; condition)
if (int a = GetValue(); a < 101) {
cout << a;
}
string str = "Hi World";
if (auto [pos, size] = pair(str.find("Hi"), str.size()); pos != string::npos) {
std::cout << pos << " Hello, size is " << size;
}
04
Inline Variables
✦
Before C++17, only inline functions existed; now there are inline variables. In our impression, C++ class static member variables cannot be initialized in header files, but with inline variables, this can be achieved:
// header file
struct A {
static const int value;
};
inline int const A::value = 10;
struct A {
inline static const int value = 10;
}
05
Fold Expressions
✦
C++17 introduced fold expressions to make variadic template programming easier:
template <typename ... Ts>
auto sum(Ts ... ts) {
return (ts + ...);
}
int a {sum(1, 2, 3, 4, 5)}; // 15
std::string a{"hello "};
std::string b{"world"};
cout << sum(a, b) << endl; // hello world
06
constexpr Lambda Expressions
✦
Before C++17, lambda expressions could only be used at runtime; C++17 introduced constexpr lambda expressions, which can be used for compile-time calculations:
int main() { // c++17 can compile
constexpr auto lamb = [] (int n) { return n * n; };
static_assert(lamb(3) == 9, "a");
}
constexpr functions have the following restrictions: the function body cannot contain assembly statements, goto statements, labels, try blocks, static variables, thread-local storage, uninitialized ordinary variables, cannot dynamically allocate memory, cannot have new or delete, etc., and cannot have virtual functions.
07
Nested Namespaces
✦
namespace A {
namespace B {
namespace C {
void func();
}
}
}
// c++17, more convenient
namespace A::B::C {
void func();
}
08
__has_include Preprocessor Expression✦
This can determine whether a certain header file exists. Code may work under different compilers, and available headers may differ between compilers, so this can be used to check:
#if defined __has_include
#if __has_include(<charconv>)
#define has_charconv 1
#include <charconv>
#endif
#endif
std::optional<int> ConvertToInt(const std::string& str) {
int value{};
#ifdef has_charconv
const auto last = str.data() + str.size();
const auto res = std::from_chars(str.data(), last, value);
if (res.ec == std::errc{} && res.ptr == last) {
return value;
}
#else
// alternative implementation...
// Other methods of implementation
#endif
return std::nullopt;
}
09
Capture Object Copies in Lambda Expressions with *this
✦
Normally, accessing class member variables in a lambda expression requires capturing this, which captures the this pointer pointing to the object reference. Normally, this may not be an issue, but in multithreading situations, if the function’s scope exceeds the object’s scope, and the object has been destructed, accessing member variables will cause problems.
struct A {
int a;
void func() {
auto f = [this] {
cout << a << endl;
};
f();
}
};
int main() {
A a;
a.func();
return 0;
}
C++17 added a new feature to capture *this, which does not hold the this pointer but holds a copy of the object, thus the lifetime is not related to the object’s lifetime:
struct A {
int a;
void func() {
auto f = [*this] {
cout << a << endl;
};
f();
}
};
int main() {
A a;
a.func();
return 0;
}
10
New Attributes
✦
In projects, we often see __declspec, attribute, #pragma directives, which are used to provide the compiler with additional information for optimizations or specific code, and can also provide hints to other developers.
struct A { short f[3]; } __attribute__((aligned(8)));
void fatal() __attribute__((noreturn));
In C++11 and C++14, there are more convenient methods:
[[carries_dependency]] allows the compiler to skip unnecessary memory fence instructions
[[noreturn]] indicates that the function will not return
[[deprecated]] generates a warning for deprecated functions
[[noreturn]] void terminate() noexcept;
[[deprecated("use new func instead")]] void func() {}
C++17 introduced three new attributes: [[fallthrough]], used in switches to indicate falling through without needing a break, allowing the compiler to ignore warnings.
switch (i) {}
case 1:
// warning
xxx;
case 2:
xxx;
// warning eliminated
[[fallthrough]];
case 3:
xxx;
break;
}
[[nodiscard]]: indicates that the marked content should not be ignored, can be used to mark functions to indicate that the return value must be handled.
[[nodiscard]] int func();
void F() {
// warning: function return value not handled
func();
}
[[maybe_unused]]: hints the compiler that the marked content may not be used temporarily, avoiding warnings.
void func1() {}
// warning eliminated
[[maybe_unused]] void func2() {}
void func3() {
int x = 1;
// warning eliminated
[[maybe_unused]] int y = 2;
}
11
std::variant
✦
Introduced from_chars and to_chars functions, directly look at the code:
#include <charconv>
int main() {
const std::string str{"123456098"};
int value = 0;
const auto res = std::from_chars(str.data(), str.data() + 4, value);
if (res.ec == std::errc()) {
cout << value << ", distance " << res.ptr - str.data() << endl;
} else if (res.ec == std::errc::invalid_argument) {
cout << "invalid" << endl;
}
str = std::string("12.34);
double val = 0;
const auto format = std::chars_format::general;
res = std::from_chars(str.data(), str.data() + str.size(), value, format);
str = std::string("xxxxxxxx");
const int v = 1234;
res = std::to_chars(str.data(), str.data() + str.size(), v);
cout << str << ", filled " << res.ptr - str.data() << " characters \n";
// 1234xxxx, filled 4 characters
}
12
std::optional
✦
C++17 added std::variant to provide similar functionality to union, but more advanced. For example, union cannot contain types like string, but std::variant can and supports more complex types such as map.
int main() {
// c++17 can compile
std::variant<int, std::string> var("hello");
cout << var.index() << endl;
var = 123;
cout << var.index() << endl;
try {
var = "world";
// Get value by type
std::string str = std::get<std::string>(var);
var = 3;
// Get value by index
int i = std::get<0>(var);
cout << str << endl;
cout << i << endl;
} catch(...) {
// xxx;
}
return 0;
}
Note: Generally, the first type of variant should have a corresponding constructor; otherwise, compilation will fail:
struct A {
A(int i){}
};
int main() {
// compilation failed
std::variant<A, int> var;
}
To avoid this situation, std::monostate can be used to simulate an empty state.
// can compile successfully
std::variant<std::monostate, A> var;
13
std::optional
✦
Sometimes there may be a need to return an object from a function:
struct A {};
A func() {
if (flag) return A();
else {
// In case of exception, how to return an exceptional value, want to return an empty one
}
}
One way is to return an object pointer, which can return nullptr in case of exception, but this involves memory management. You may use smart pointers, but a more convenient way here is std::optional.
std::optional<int> StoI(const std::string &s) {
try {
return std::stoi(s);
} catch(...) {
return std::nullopt;
}
}
void func() {
std::string s{"123"};
std::optional<int> o = StoI(s);
if (o) {
cout << *o << endl;
} else {
cout << "error" << endl;
}
}
14
std::any✦
C++17 introduced any to store a single value of any type.
int main() {
// c++17 can compile
std::any a = 1;
cout << a.type().name() << " " << std::any_cast<int>(a) << endl;
a = 2.2f;
cout << a.type().name() << " " << std::any_cast<float>(a) << endl;
if (a.has_value()) {
cout << a.type().name();
}
a.reset();
if (a.has_value()) {
cout << a.type().name();
}
a = std::string("a");
cout << a.type().name() << " " << std::any_cast<std::string>(a) << endl;
return 0;
}
15
std::apply✦
Using std::apply can expand a tuple to pass as function parameters.
int add(int first, int second) { return first + second; }
auto add_lambda = [](auto first, auto second) { return first + second; };
int main() {
std::cout << std::apply(add, std::pair(1, 2)) << '\n';
std::cout << add(std::pair(1, 2)) << "\n"; // error
std::cout << std::apply(add_lambda, std::tuple(2.0f, 3.0f)) << '\n';
}
16
std::make_from_tuple✦
Using make_from_tuple can expand a tuple as constructor parameters.
struct Foo {
Foo(int first, float second, int third) {
std::cout << first << ", " << second << ", " << third << "\n";
}
};
int main() {
auto tuple = std::make_tuple(42, 3.14f, 0);
std::make_from_tuple<Foo>(std::move(tuple));
}
17
std::string_view✦
Passing a string usually triggers object copy operations, and copying large strings can trigger heap memory allocations, greatly affecting runtime efficiency. With string_view, copying operations can be avoided; simply pass string_view during the process.
void func(std::string_view stv) { cout << stv << endl; }
int main(void) {
std::string str = "Hello World";
std::cout << str << std::endl;
std::string_view stv(str.c_str(), str.size());
cout << stv << endl;
func(stv);
return 0;
}
18
as_const✦
C++17 allows using as_const to convert lvalues to const types.
std::string str = "str";
const std::string&& constStr = std::as_const(str);
19
file_system✦
C++17 officially included file_system in the standard, providing most functionalities related to files, essentially covering everything:
namespace fs = std::filesystem;
fs::create_directory(dir_path);
fs::copy_file(src, dst, fs::copy_options::skip_existing);
fs::exists(filename);
fs::current_path(err_code);
20
std::shared_mutex
✦
C++17 introduced shared_mutex, which can implement read-write locks.

