What's new in Modern C++ (C++17) Part II?
As promised in the previous blog post, there are many more features for C++17. This time around, we will look at some others new notable features.
Let's continue our discovery of C++17 features;
string_view
This is an unfortunate side-effect of C++ being interoperable with C language where we need to accept an old-school list of characters in the format of const char *
.
In old C++, we probably need to do something similar to this;
Bar getBar(const string& s);
Bar getBar(const char* s);
Bar getBar(const char* s, int length);
In new C++, this got streamlined and it becomes much much simpler;
Bar getBar(string_view str);
This is not new by any means, it has been done before by the introduction of boost::string_ref
, QStringRef
etc. string_view
is a standardized version of those ✨.
How to use it? I think you can just throw string_view
in every place that we used to do const std::string&
. It should work like a charm.
Optional
Let's say we have a need to design an API that needs to communicate with user for a failure case.
Bar getBar(string_view str);
Maybe using int
or bool
to communicate back to our user of this API? (using -1 and false
to indicate error? 😶)
int getBar(Foo f, Bar& bar); //💩
bool getBar(Foo f, Bar& bar); //💢
Or slightly better use pointer to communicate this back to the user?
//return null on failure
unique_ptr<Bar> getBar(Foo f);
Both abovementioned options are not nice, don't we have a better tool for this? Answer: Yes, we do. Introducing optional.
optional<Bar> getBar(Foo f);
How do we use this function?
auto bar = getBar(f);
if (bar) {
//do something with a real value, access real value by using `*bar`
}
or nicer with one-liner.
if (auto bar = getBar(f); bar) {
cout << *bar;
} else {
//if bar is null (`std::nullopt`), we can do some more thing in here.
}
This makes our interface nicer to work with.
Variant & Any
Let's say we have a need to represent data that is dynamically changed at runtime. (I am looking at you JSON 👀).
JSON's value could be the following; a string, a number, an object (JSON object), an array, a boolean !
Let's do that with old C++;
struct JsonValue {
union Value {
int n,
string s,
JsonValue j,
vector<JsonValue> arr,
bool b,
};
// you need a type to let the call-site know what type it represents
enum class Type {
INT,
STRING,
JSON,
ARRAY,
BOOL
};
Value value;
Type type;
}
With C++17, this is magically represented by using variant
.
struct JsonValue {
variant<int, string, JsonValue, vector<JsonValue>, bool> value;
}
How to use? visit
is a way to go! We can use either lambda or struct like this;
struct JsonVisitor {
void operator()(int i) const {
//do something when it is `int`
}
void operator()(string s) const {
// it is `string`
}
void operator()(bool b) const {
// it is `bool`
}
};
variant<int, string, JsonValue, vector<JsonValue>, bool> value;
value = 30;
visit(JsonVisitor{}, value); //<-- handled by JsonVisitor::operator()(int i)
value = "string";
visit(JsonVisitor{}, value); //<-- JsonVisitor::operator()(string s)
How about Any? While variant
creates a contract that it promises to hold just only limited types, with any
, it can hold pretty much anything!!
any v = createValue();
if (v.type() == typeid(int)) {
auto i = any_cast<int>(v);
}
Or you can do some fancy stuff like this
//things can be multiple objects ...
vector<Thing*> v; //instead of using this 🤔
vector<any> v; //<- you may use this which can hold anything 😯
Filesystem
Filesystem is C++ before C++17 is a wild wild west. POSIX and Windows implementation are quite different. This leads to an introduction of preprocessing macros like _WIN32
, __APPLE__
or __posix
. The maintenance for these branching is suboptimal and giving us headache. 😢
Fear no more, in C++17 with introduction of filesystem namespace std::filesystem
, we are having a consistent implementation.
For listing, files and/or folders in directory, one might do
void printContentOf(const filesystem::path& path) {
for (auto const& item : filesystem::director_iterator(p)) {
auto status = item.status();
if (filesystem::is_regular_file(status) {
cout << "File : " << item.path().filename() << "\n";
} else if (filesystem::is_directory(status)) {
cout << "Dir : " << item.path().filename() << "\n";
}
}
}
No more include windows.h
, no more using struct dirent
, only joy 😄...
As you can see, I think C++17 is a big step into the right direction for C++. It makes the new code nicer and easier to write, yet maintain the top-notch preformance. If you haven't checked C++ for a while, I suggest you try to do it now. C++ has grown and improve a lot, it might give you a bit of surprise. 🎉
kittinunf 😼
Clap to support the author, help others find it, and make your opinion count.