Modern C++
Standard Practices from Standard Library


4. C++17 new features


Stefano Lusardi, Software Developer

Agenda


  • Filesystem: file and path
  • Utilities: optional, variant and any
  • New language features

Filesystem



The filesystem library allows to perform operations on file systmem components.


Filesystem is based on the three elements:

  • file
  • file name
  • path


See the full reference here

Simple filesystem examples



#include <filesystem>
namespace fs = std::filesystem;
		


Retrieve paths


fs::path path1 = fs::current_path();
fs::path path2 = "/path/to/myFile.cf";
fs::path path3 = "C:\\path\\to\\my\\Folder\\"; 
		


Create/Delete nested directories


std::string myDir = "root/first/second";
bool isCreated = fs::create_directories(myDir);
auto numDeletedFolders = fs::remove_all("root"); // recursive
		


Basic file information


bool a = fs::exists(myDir)
bool b = fs::is_directory(myDir)
bool c = fs::is_symlink(myDir)
		


Permissions
The class fs::perms can hold values such as: owner_read, owner_write, group_read, group_write, ...
fs::perms is a bitmask type, so you can read its value as:


fs::perms myPerms = fs::status("myFile.txt").permissions();
bool canOwnerRead = 
    (perm & fs::perms::owner_read) != fs::perms::none;
	


Read/Write times


fs::path myPath = fs::current_path() / "myFile.txt";
std::ofstream(path.c_str()); 
auto writeTime = fs::last_write_time(path);
	


Space information (capacity, free, and available):


fs::space_info root_info = fs::space("/");
std::cout << root_info.capacity;
std::cout << root_info.free;
std::cout << root_info.available;
	

Utilities



std::optional<T>

It is a template class that can wrap any type T and it is able to indicate if the wrapped value is initialized or not.


std::optional<int> opt1 = std::make_optional<int>(42);
std::optional<int> opt2 { 42 }; // Implicit initialization


Typical usage is to handle function that may fail (return prematurely) without returning a value.

Example: check if a function returns a valid value



std::optional<std::string> getString(bool ok) {
    if (ok) { return "OptionalString"; }
    return { }; // create an empty std::optional<std::string>
}

auto str1 = getString(true);
std::cout << str1.value(); // "OptionalString"

auto str2 = getString(false);
std::cout << str2.value(); // throws std::bad_optional_access 
std::cout << str2.value_or("NoString"); // "NoString"

if (str2.has_value()) { /*...*/ } // explicit safety check
if (str2) { /*...*/ } // (bool conversion) implicit safety check



std::variant<T>

Template class that enforces a type-safe union.
It can hold a value of one of its alternative types, or none


std::variant<int, std::string> var = 42;
std::cout << std::get<int>(var); // "42"

var = "myString"s;
std::cout << std::get<std::string>(var); //"myString"

int x = std::get<int>(var); // throws std::bad_variant_access

Now you can forget about old (and unsafe) unions Note that it is now possible to implement easily a visitor pattern using std::visit

Example: check current type


std::variant<int, std::string> var { "myString"; }
std::cout << std::holds_alternative<int>(var); // false
std::cout << std::holds_alternative<std::string>(var); // true


Example: try to get the current value


std::variant<int, float> var { 42 };
if(auto pvar = std::get_if<int>(&var))
    std::cout << "variant value: " << *pvar; // "42"
else 
    std::cout << "failed to get value!";


std::any


Class template that can wrap a value of any type in a type-safe way.



std::any a(42); // initialize with an int
a = std::string("MyString"); // store a std::string

std::cout << std::any_cast<std::string>(a); // "MyString"
std::cout << std::any_cast<int>(a); // throws std::bad_any_cast

Before C++17 it was possible to achieve a similar behavior using void* but this is not type-safe (dangerous!)

Example: check current type


std::any = 42;

if (a.has_value())
    std::cout << a.type().name(); // "i" (int)
else
    a.reset(); // destroy wrapped object

	
if (a.type() == typeid(int))
    std::cout << "int: " << std::any_cast<int>(a);
else if (a.type() == typeid(std::string))
    std::cout << "string: " << std::any_cast<std::string>(a);

New language features



if/switch initialization statement

It has always been possible to initialize a variable directly in a for loop as:


for (int i = 0; i < SIZE; ++i) { /*...*/ }

This is very convenient and safe: it prevents that the i variable is used outside the loop scope.


Now it is possible to do the same with both if and switch statements.


Example: in-scope if initialization


if (const auto key = getKey(); key!=42) { /* use key */ }
else { /* use key */ }
// "key" is no longer defined here


std::map<int, std::string> myMap {{0, "z"}, {42, "f"}};
if (const auto it = myMap.find(42); it != myMap.end())
    std::cout << it->second; // "f"


The variable declared in the if (or switch) statement can only be used in the if or else branch since it is not visible to the outer scope.


Structured Bindings


Create a set of "alias names" from a tuple or from a struct



Example: C++11 binding with std::tie


std::set<int> mySet { };

// can't be declared const nor reference types
std::set<int>::iterator iter;
bool isInserted; 

std::pair<std::set<int>::iterator, bool> it = mySet.insert(0)
iter = it.first;
isInserted = it.second;

std::tie(iter, isInserted) = mySet.insert(42);
std::cout << isInserted; // "true"

std::tie(std::ignore, isInserted) = mySet.insert(42);
std::cout << isInserted; // "false"


Example: C++17 Structured Bindings


std::set<int> mySet { };
const auto& [iter, isInserted] = mySet.insert(42);

// Now "isInserted" can be const
// "iter" and "isInserted" can be reference types


Example: get values from a struct


struct Point2D { int x; int y; };
auto p = Point2D(1, 2);

const auto& [lat, lon] = p;
std::cout << lat; // 1
std::cout << lon; // 2

Example: iterate on a map


std::map<int, char> myMap {{1, 'a'}, {2, 'b'}};    
for (const auto& [key, val] : myMap) 
{  
    std::cout << key << val; 
} 
// 1'a'
// 2'b'

Example: combine with if-init


if (auto [iter, isInserted] = mySet.insert(value); isInserted)
{ 
    /* value has been inserted at position "iter" */ 
}
else 
{ 
    /* value has not been inserted */ 
}
// "iter" and "isInserted" are out of scope here


Multi-Value return


Function returning tuple that can be unpacked using Structured Bindings


No more need to pass input reference to functions to retrieve multiple outputs


Example: tuple-return


std::tuple<int, std::string, float> func(){
	/* ... */
	return { 42, "myString", 1.234}
}

const auto& [a, b, c] = func();
std::cout << a; // 42
std::cout << b; // "myString"
std::cout << c; // 1.234 

Example: combine with if-init


if (const auto& [a, b, c] = func(); a>41)
{ /* ... */ }

Thank you!

Questions?