c++17带来的代码变化 https://github.com/tvaneerd/cpp17_in_TTs/blob/master/ALL_IN_ONE.md Descriptions of C++17 features, presented mostly in "Tony Tables" (hey, the tables were my idea, but their name wasn't :-). There are actually **over 100 changes in C++17**, only some of them are listed here. Caveat1: At time of writing, *C++17 was completed, but not signed off yet.* There may be slight differences, although _highly_ unlikely (modulo defects). Caveat2: *I make mistakes.* This is more likely :-) Created with the support of my employer, [Christie Digital Systems](https://www.christiedigital.com/en-us/business/solutions/projection-mapping). --- if-init ---
C++ |
---|
{
if (Foo * ptr = get_foo())
use(*ptr);
more_code();
}
|
C++ |
---|
{
{
QVariant var = getAnswer();
if (var.isValid())
use(var);
}
more_code();
}
|
C++ | C++17 |
---|---|
{
{
QVariant var = getAnswer();
if (var.isValid())
use(var);
}
more_code();
}
|
{
if (QVariant var = getAnswer(); var.isValid())
use(var);
more_code();
}
|
C++17 |
---|
{
switch (Device dev = get_device(); dev.state())
{
case sleep: /*...*/ break;
case ready: /*...*/ break;
case bad: /*...*/ break;
}
}
|
C++14 | C++17 | |
---|---|---|
tuple<int, string> func();
auto tup = func();
int i = get<0>(tup);
string s = get<1>(tup);
use(s, ++i);
|
tuple<int, string> func();
int i;
string s;
std::tie(i,s) = func();
use(s, ++i);
|
tuple<int, string> func();
auto [ i, s ] = func();
use(s, ++i);
|
C++17 | compiler |
---|---|
pair<int, string> func();
auto [ i, s ] = func();
use(s, ++i);
|
pair<int, string> func();
auto __tmp = func();
auto & i = get<0>(__tmp);
auto & s = get<1>(__tmp);
use(s, ++i);
|
C++17 | compiler |
---|---|
#include <string>
#include <iostream>
struct Foo
{
int x = 0;
std::string str = "world";
~Foo() { std::cout << str; }
};
int main()
{
auto [ i, s ] = Foo();
std::cout << "hello ";
s = "structured bindings";
}
|
#include <string>
#include <iostream>
struct Foo
{
int x = 0;
std::string str = "world";
~Foo() { std::cout << str; }
};
int main()
{
auto __tmp = Foo();
std::cout << "hello ";
__tmp.str = "structured bindings";
}
|
Output |
hello structured bindings |
C++17 | compiler |
---|---|
struct X { int i = 0; };
X makeX();
X x;
auto [ b ] = makeX();
b++;
auto const [ c ] = makeX();
c++;
auto & [ d ] = makeX();
d++;
auto & [ e ] = x;
e++;
auto const & [ f ] = makeX();
f++;
|
struct X { int i = 0; };
X makeX();
X x;
auto __tmp1 = makeX();
__tmp1.i++;
auto const __tmp2 = makeX();
__tmp2.i++; //error: can't modify const
auto & __tmp3 = makeX(); //error: non-const ref cannot bind to temp
auto & _tmp3 = x;
x.i++;
auto const & _tmp4 = makeX();
__tmp4.i++; //error: can't modify const
|
C++17 | compiler |
---|---|
struct Foo {
int x;
string str;
};
Foo func();
auto [ i, s ] = func();
use(s, ++i);
|
struct Foo {
int x;
string str;
};
Foo func();
Foo __tmp = func();
auto & i = __tmp.x;
auto & s = __tmp.str;
use(s, ++i);
|
C++17 |
---|
class Foo {
// ...
public:
template <int N> auto & get() /*const?*/ { /*...*/ }
};
// or get outside class
template<int N> auto & get(Foo /*const?*/ & foo) { /*...*/ }
//...
// tuple_size/element specialized
// yes, in namespace std
namespace std {
// how many elements does Foo have
template<> struct tuple_size<Foo> { static const int value = 3; }
// what type is element N
template<int N> struct tuple_element<N, Foo> { using type = ...add code here...; }
}
Foo func();
auto [ i, s ] = func();
use(s, ++i);
|
etc |
---|
int arr[4] = { /*...*/ };
auto [ a, b, c, d ] = arr;
auto [ t, u, v ] = std::array<int,3>();
// now we're talkin'
for (auto && [key, value] : my_map)
{
//...
}
|
C++14 | C++17 |
---|---|
class Foo {
int myInt;
string myString;
public:
int const & refInt() const
{ return myInt; }
string const & refString() const
{ return myString; }
};
namespace std
{
template<> class tuple_size<Foo>
: public integral_constant<int, 2>
{ };
template<int N> class tuple_element<N, Foo>
{
public:
using type =
conditional_t<N==0,int const &,string const &>;
};
}
template<int N> std::tuple_element_t<N,Foo>
get(Foo const &);
// here's some specializations (the real stuff)
template<> std::tuple_element_t<0,Foo>
get<0>(Foo const & foo)
{
return foo.refInt();
}
template<> std::tuple_element_t<1,Foo>
get<1>(Foo const & foo)
{
return foo.refString();
}
|
class Foo {
int myInt;
string myString;
public:
int const & refInt() const
{ return myInt; }
string const & refString() const
{ return myString; }
};
namespace std
{
template<> class tuple_size<Foo>
: public integral_constant<int, 2>
{ };
template<int N> class tuple_element<N, Foo>
{
public:
using type =
conditional_t<N==0,int const &,string const &>;
};
}
template<int N> auto & get(Foo const & foo)
{
static_assert(0 <= N && N < 2, "Foo only has 2 members");
if constexpr (N == 0) // !! LOOK HERE !!
return foo.refInt();
else if constexpr (N == 1) // !! LOOK HERE !!
return foo.refString();
}
|
C++14 | C++17 |
---|---|
pair<int, string> is1 = pair<int, string>(17, "hello");
auto is2 = std::pair<int, string>(17, "hello");
auto is3 = std::make_pair(17, string("hello"));
auto is4 = std::make_pair(17, "hello"s);
|
pair<int, string> is1 = pair(17, "hello");
auto is2 = pair(17, "hello"); // !! pair<int, char const *>
auto is3 = pair(17, string("hello"));
auto is4 = pair(17, "hello"s);
|
template<typename T>
struct Thingy
{
T t;
};
// !! LOOK HERE !!
Thingy(const char *) -> Thingy<std::string>;
Thingy thing{"A String"}; // thing.t is a `std::string`.
_(example from "Nicol Bolas")_
#### Implicit Deduction Guides
For any `templateC++14 | C++17 |
---|---|
template <typename T, T v>
struct integral_constant
{
static constexpr T value = v;
};
integral_constant<int, 2048>::value
integral_constant<char, 'a'>::value
|
template <auto v>
struct integral_constant
{
static constexpr auto value = v;
};
integral_constant<2048>::value
integral_constant<'a'>::value
|
How do you write `sum()` ? |
---|
auto x = sum(5, 8);
auto y = sum(a, b, 17, 3.14, etc);
|
C++14 | C++17 |
---|---|
auto sum() { return 0; }
template <typename T>
auto sum(T&& t) { return t; }
template <typename T, typename... Rest>
auto sum(T&& t, Rest&&... r) {
return t + sum(std::forward<Rest>(r)...);
}
|
template <typename... Args>
auto sum(Args&&... args) {
return (args + ... + 0);
}
|
C++14 | C++17 |
---|---|
namespace A {
namespace B {
namespace C {
struct Foo { };
//...
}
}
}
|
namespace A::B::C {
struct Foo { };
//...
}
|
C++14 | C++17 |
---|---|
static_assert(sizeof(short) == 2, "sizeof(short) == 2")
|
static_assert(sizeof(short) == 2)
|
Output | |
static assertion failure: sizeof(short) == 2 |
C++14 | C++17 |
---|---|
// foo.h
extern int foo;
// foo.cpp
int foo = 10;
|
// foo.h
inline int foo = 10;
|
C++14 | C++17 |
---|---|
// foo.h
struct Foo {
static int foo;
};
// foo.cpp
int Foo::foo = 10;
|
// foo.h
struct Foo {
static inline int foo = 10;
};
|
C++17 |
---|
// header <mutex>
namespace std
{
template <typename M>
struct lock_guard
{
explicit lock_guard(M & mutex);
// not copyable, not movable:
lock_guard(lock_guard const & ) = delete;
//...
}
}
// your code
lock_guard<mutex> grab_lock(mutex & mtx)
{
return lock_guard<mutex>(mtx);
}
mutex mtx;
void foo()
{
auto guard = grab_lock(mtx);
/* do stuff holding lock */
}
|
C++14 | C++17 |
---|---|
switch (device.status())
{
case sleep:
device.wake();
// fall thru
case ready:
device.run();
break;
case bad:
handle_error();
break;
}
|
switch (device.status())
{
case sleep:
device.wake();
[[fallthrough]];
case ready:
device.run();
break;
case bad:
handle_error();
break;
}
|
Compiler | Compiler |
warning: case statement without break |
C++14 | C++17 |
---|---|
struct SomeInts
{
bool empty();
void push_back(int);
//etc
};
void random_fill(SomeInts & container,
int min, int max, int count)
{
container.empty(); // empty it first
for (int num : gen_rand(min, max, count))
container.push_back(num);
}
|
struct SomeInts
{
[[nodiscard]] bool empty();
void push_back(int);
//etc
};
void random_fill(SomeInts & container,
int min, int max, int count)
{
container.empty(); // empty it first
for (int num : gen_rand(min, max, count))
container.push_back(num);
}
|
Compiler | C++17 Compiler |
warning: ignoring return value of 'bool empty()' |
C++14 | C++17 |
---|---|
struct MyError {
std::string message;
int code;
};
MyError divide(int a, int b) {
if (b == 0) {
return {"Division by zero", -1};
}
std::cout << (a / b) << '\n';
return {};
}
divide(1, 2);
|
struct [[nodiscard]] MyError {
std::string message;
int code;
};
MyError divide(int a, int b) {
if (b == 0) {
return {"Division by zero", -1};
}
std::cout << (a / b) << '\n';
return {};
}
divide(1, 2);
|
Compiler | C++17 Compiler |
warning: ignoring return value of function declared with 'nodiscard' attribute |
C++14 | C++17 |
---|---|
bool res = step1();
assert(res);
step2();
etc();
|
[[maybe_unused]] bool res = step1();
assert(res);
step2();
etc();
|
Compiler | C++17 Compiler |
warning: unused variable 'res' |
C++17 |
---|
[[maybe_unused]] void f()
{
/*...*/
}
int main()
{
}
|
C++14 | C++17 |
---|---|
Foo parseFoo(std::string const & input);
Foo parseFoo(char const * str);
Foo parseFoo(char const * str, int length);
Foo parseFoo(MyString const & str);
|
Foo parseFoo(std::string_view input);
// I would say don't offer this interface, but:
Foo parseFoo(char const * str, int length)
{
return parseFoo(string_view(str,length));
}
class MyString {
//...
operator string_view() const
{
return string_view(this->data, this->length);
}
};
|
C++14 | C++17 |
---|---|
// returns default Foo on error
Foo parseFoo(std::string_view in);
// throws parse_error
Foo parseFoo(std::string_view in);
// returns false on error
bool parseFoo(std::string_view in, Foo & output);
// returns null on error
unique_ptr<Foo> parseFoo(std::string_view in);
|
std::optional<Foo> parseFoo(std::string_view in);
|
C++17 |
---|
optional ofoo = parseFoo(str);
if (ofoo)
use(*ofoo);
|
// nicer with new if syntax:
if (optional ofoo = parseFoo(str); ofoo)
use(*ofoo);
|
optional<int> oi = parseInt(str);
std::cout << oi.value_or(0);
|
C++14 | C++17 |
---|---|
struct Stuff
{
union Data {
int i;
double d;
string s; // constructor/destructor???
} data;
enum Type { INT, DOUBLE, STRING } type;
};
|
struct Stuff
{
std::variant<int, double, string> data;
};
|
C++14 | C++17 |
---|---|
void handleData(int i);
void handleData(double d);
void handleData(string const & s);
//...
switch (stuff.type)
{
case INT:
handleData(stuff.data.i);
break;
case DOUBLE:
handleData(stuff.data.d);
break;
case STRING:
handleData(stuff.data.s);
break;
}
|
void handleData(int i);
void handleData(double d);
void handleData(string const & s);
//...
std::visit([](auto const & val) { handleData(val); }, stuff.data);
// can also switch(stuff.data.index())
|
How the above lambda works |
---|
struct ThatLambda
{
void operator()(int const & i) { handleData(i); }
void operator()(double const & d) { handleData(d); }
void operator()(string const & s) { handleData(s); }
};
ThatLambda thatLambda;
std::visit(thatLambda, stuff.data);
|
C++17 |
---|
if (holds_alternative<int>(data))
int i = get<int>(data);
// throws if not double:
double d = get<double>(data);
|
C++17 |
---|
std::variant<Foo, Bar> var; // calls Foo()
// (or doesn't compile if no Foo())
Bar bar = makeBar();
var = bar; // calls ~Foo() and Bar(Bar const &)
// (what if Bar(Bar const & b) throws?)
var = Foo(); // calls ~Bar() and move-ctor Foo(Foo &&)
// (what if Foo(Foo && b) throws? - even though moves shouldn't throw)
var = someFoo; // calls Foo::operator=(Foo const &)
std::variant<Foo, std::string> foostr;
foostr = "hello"; // char * isn't Foo or string
// yet foostr holds a std::string
|
C++14 | C++17 | C++17 |
---|---|---|
void * v = ...;
if (v != nullptr) {
// hope and pray it is an int:
int i = *reinterpret_cast<int*>(v);
}
|
std::any v = ...;
if (v.has_value()) {
// throws if not int
int i = any_cast<int>(v);
}
|
std::any v = ...;
if (v.type() == typeid(int)) {
// definitely an int
int i = any_cast<int>(v);
}
|
C++14 | C++17 |
---|---|
// can hold Circles, Squares, Triangles,...
std::vector<Shape *> shapes;
|
// can hold Circles, Squares, Triangles, ints, strings,...
std::vector<any> things;
|
C++14 Windows | C++17 |
---|---|
#include <windows.h>
void copy_foobar() {
std::wstring dir = L"\\sandbox";
std::wstring p = dir + L"\\foobar.txt";
std::wstring copy = p;
copy += ".bak";
CopyFile(p, copy, false);
std::string dir_copy = dir + ".bak";
SHFILEOPSTRUCT s = { 0 };
s.hwnd = someHwndFromSomewhere;
s.wFunc = FO_COPY;
s.fFlags = FOF_SILENT;
s.pFrom = dir.c_str();
s.pTo = dir_copy.c_str();
SHFileOperation(&s);
}
void display_contents(std::wstring const & p) {
std::cout << p << "\n";
std::wstring search = p + "\\*";
WIN32_FIND_DATA ffd;
HANDLE hFind =
FindFirstFile(search.c_str(), &ffd);
if (hFind == INVALID_HANDLE_VALUE)
return;
do {
if ( ffd.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY) {
std::cout << " " << ffd.cFileName << "\n";
} else {
LARGE_INTEGER filesize;
filesize.LowPart = ffd.nFileSizeLow;
filesize.HighPart = ffd.nFileSizeHigh;
std::cout << " " << ffd.cFileName
<< " [" << filesize.QuadPart
<< " bytes]\n";
}
} while (FindNextFile(hFind, &ffd) != 0);
}
|
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
void copy_foobar() {
fs::path dir = "/";
dir /= "sandbox";
fs::path p = dir / "foobar.txt";
fs::path copy = p;
copy += ".bak";
fs::copy(p, copy);
fs::path dir_copy = dir;
dir_copy += ".bak";
fs::copy(dir, dir_copy, fs::copy_options::recursive);
}
void display_contents(fs::path const & p) {
std::cout << p.filename() << "\n";
if (!fs::is_directory(p))
return;
for (auto const & e: fs::directory_iterator{p}) {
if (fs::is_regular_file(e.status())) {
std::cout << " " << e.path().filename()
<< " [" << fs::file_size(e) << " bytes]\n";
} else if (fs::is_directory(e.status())) {
std::cout << " " << e.path().filename() << "\n";
}
}
}
|
C++14 POSIX | |
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
void copy_foobar() {
// [TODO]
// to copy file, use fread / fwrite
// how to copy directory...?
}
void display_contents(std::string const & p) {
std::cout << p << "\n";
struct dirent *dp;
DIR *dfd;
if ((dfd = opendir(p.c_str()) == nullptr)
return;
while((dp = readdir(dfd)) != nullptr) {
struct stat st;
string filename = p + "/" + dp->d_Name;
if (stat(filename.c_str(), &st) == -1)
continue;
if ((st.st_mode & S_IFMT) == S_IFDIR)
std::cout << " " << filename << "\n";
} else {
std::cout << " " << filename
<< " [" << st.st_size
<< " bytes]\n";
}
}
}
|
C++14 | C++17 |
---|---|
std::for_each(first, last,
[](auto & x){ process(x); }
);
std::copy(first, last, output);
std::sort(first, last);
std::transform(xfirst, xlast, yfirst,
[=](double xi, double yi){ return a * xi + yi; }
);
|
std::for_each(std::execution::par, first, last,
[](auto & x){ process(x); }
);
std::copy(std::execution::par, first, last, output);
std::sort(std::execution::par, first, last);
std::transform(std::execution::par_unseq, xfirst, xlast, yfirst,
[=](double xi, double yi){ return a * xi + yi; }
);
|
C++14 | Concepts TS |
---|---|
//
// T must be a Random Access Iterator
// otherwise you will either get the wrong answer,
// or, most likely, terrible compiler errors
//
template <typename T>
auto binarySearch(T first, T second)
{
//...
}
|
template <RandomAccessIterator T>
auto binarySearch(T first, T second)
{
//...
}
|
error: syntax error '[' unexpected error: gibberish error: more compiler gibberish error: for pages and pages ... |
error: MyIter does not model RandomAccessIterator |