0% found this document useful (0 votes)
196 views116 pages

Practical C++17 - Jason Turner - CppCon 2017

Practical C++ 17 Jason Turner CppCon 2017

Uploaded by

G
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
196 views116 pages

Practical C++17 - Jason Turner - CppCon 2017

Practical C++ 17 Jason Turner CppCon 2017

Uploaded by

G
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 116

Practical C++17

Copyright Jason Turner @le icus 1.1


Jason Turner
Co-host of CppCast http://cppcast.com
Host of C++ Weekly https://www.youtube.com/c/JasonTurner-le icus
Co-creator of ChaiScript http://chaiscript.com
Curator of http://cppbestpractices.com
Microso MVP for C++ 2015-present

Copyright Jason Turner @le icus 1.2


Jason Turner
Independent and available for training or contracting
http://articles.emptycrate.com/idocpp
http://articles.emptycrate.com/training.html
Next training: 2 days of best practices @ CppCon 2017

Copyright Jason Turner @le icus 1.3


ChaiScript
Embedded scripting language co-designed by me specifically for C++
Supports Visual Studio, clang, GCC
Runs on Windows, Linux, MacOS, FreeBSD, Haiku
Currently requires C++14
Header only - no external deps
Designed for integration with C++
All types are the same and directly shared between script and C++
( double , std::string , std::function<> , etc)

Copyright Jason Turner @le icus 1.4


ChaiScript
My proving ground for testing best practices.
About 25k lines of C++
Has evolved from C++03 + Boost -> C++11 -> C++14 and onward to
C++17
Complex template usage for automatic function type deduction

Copyright Jason Turner @le icus 1.5


ChaiScript
Full example:
1 #include <chaiscript/chaiscript.hpp>
2
3 std::string greet(const std::string &t_name) {
4 return "Hello " + t_name;
5 }
6
7 int main() {
8 ChaiScript::chaiscript chai;
9 chai.add(chaiscript::fun(&greet), "greet");
10 chai.eval(R"(print(greet("Jason")))");
11 }

Copyright Jason Turner @le icus 1.6


About my Talks
Move to the front!
Please interrupt and ask questions

Copyright Jason Turner @le icus 1.7


Slack

Copyright Jason Turner @le icus 2.1


C++17 Overview

Copyright Jason Turner @le icus 3.1


Structured Bindings

Copyright Jason Turner @le icus 4.1


Structured Bindings
1 auto [a,b,c] = <expression>;

Can be used to automatically split a structure into multiple variables


Added in C++17

Copyright Jason Turner @le icus 4.2


if-init expressions

Copyright Jason Turner @le icus 5.1


if-init expressions
1 if (auto [key, value] = *my_map.begin(); key == "mykey"){}

Added in C++17
Also works for switch conditions

Copyright Jason Turner @le icus 5.2


Standard Library Changes

Copyright Jason Turner @le icus 6.1


emplace_back (C++17)
1 container.emplace_back();
2 auto &val = container.back();

Becomes:
1 auto &val = container.emplace_back();

C++17 modified emplace_back and emplace_front to return references to


the objects that were just created for sequence containers.

Copyright Jason Turner @le icus 6.2


std::string_view added in C++17
Provides a light weight std::string -like view into an array of characters.

It can be constructed from a const char * or automatically converted to


from an std::string or from a pointer and a length.
1 void use_string(std::string_view sv);

1 use_string("hello world");
2 use_string(some_std_string);

Intended to be the new language for passing string-like things.

Copyright Jason Turner @le icus 6.3


Nested Namespaces

Copyright Jason Turner @le icus 7.1


Nested Namespaces
C++17 adds the ability to do:
1 namespace Namespace::Nested {
2 }

Which allows for more compact namespace declarations.

Copyright Jason Turner @le icus 7.2


Class Template Type
Deduction

Copyright Jason Turner @le icus 8.1


Class Template Type Deduction
C++17 class template type deduction
1 template<typename First, typename Second>
2 struct Pair {
3 Pair(First t_first, Second t_second)
4 : first(std::move(t_first)), second(std::move(t_second))
5 {}
6
7 First first;
8 Second second;
9 };
10
11 int main() {
12 Pair p{1, 2.3}; /// template parameters not needed
13 }

Copyright Jason Turner @le icus 8.2


Class Template Deduction Guides
Deduction guides tell the compiler how to deduce a class template from
a set of parameters.
1 template<typename P1, typename P2>
2 Pair(P1 &&p1, P2 &&p2) -> Pair<std::decay_t<P1>, std::decay_t<P2>>;

Copyright Jason Turner @le icus 8.3


Class Template Deduction Guides
Which helps for cases like this:
1 template<typename First, typename Second>
2 struct Pair {
3 template<typename P1, typename P2>
4 Pair(P1 &&p1, P2 &&p2)
5 : first(std::forward<P1>(p1)), second(std::forward<P2>(p2))
6 {}
7
8 First first;
9 Second second;
10 };
11
12 template<typename P1, typename P2> ///
13 Pair(P1 &&p1, P2 &&p2) -> Pair<std::decay_t<P1>, std::decay_t<P2>>; ///
14
15 int main() {
16 Pair p{1, 2.3}; /// deduction now succeeds
17 }

Copyright Jason Turner @le icus 8.4


if constexpr

Copyright Jason Turner @le icus 9.1


if constexpr
A compile-time conditional block
1 if constexpr( /* constant expression */ ) {
2 // if true, this block is compiled
3 } else {
4 // if false, this block is compiled
5 }

Copyright Jason Turner @le icus 9.2


Fold Expressions

Copyright Jason Turner @le icus 10.1


Fold Expressions
1 ( ... <op> <pack expression> ) // unary left fold
2 ( <pack expression> <op> ... ) // unary right fold
3 ( <init> <op> ... <op> <pack expression> ) // binary left fold
4 ( <pack expression> <op> ... <op> <init> ) // binary right fold

Allows for "folding" of a variadic parameter pack into a single value.


Allowed operations:
+ - * / % ^ & | << >> += -= *= /= %= ^= &= |= <<= >>= = == != < >
<= >= && || , .* ->*

Copyright Jason Turner @le icus 10.2


Unary le fold
1 ( ... <op> <pack expression> ) // unary left fold

1 ( ... && args )

Expands to:
1 ( ( arg1 && arg2 ) && arg3 )

Copyright Jason Turner @le icus 10.3


Unary right fold
1 ( <pack expression> <op> ... ) // unary right fold

1 ( args && ... )

Expands to:
1 ( arg1 && ( arg2 && arg3 ) )

Copyright Jason Turner @le icus 10.4


Binary le fold
1 ( <init> <op> ... <op> <pack expression> )

1 ( true && ... && args )

Expands to:
1 ( ( ( true && arg1 ) && arg2 ) && arg3 )

Copyright Jason Turner @le icus 10.5


Binary right fold
1 ( <pack expression> <op> ... <op> <init> )

1 ( args && ... && true )

Expands to:
1 ( arg1 && ( arg2 && ( arg3 && true ) ) )

Copyright Jason Turner @le icus 10.6


noexcept is now part of the
type system

Copyright Jason Turner @le icus 11.1


noexcept In The Type System
1 void use_func(void (*func)() noexcept);
2 void my_func();
3
4 use_func(&my_func);

This will no longer compile in C++17, a new overload is needed:


1 void use_func(void (*func)());

noexceptfunction pointer can be converted to non- noexcept , but not the


other way around.

Copyright Jason Turner @le icus 11.2


Feature Impact

Copyright Jason Turner @le icus 12.1


Which C++ 17 Had The Most Impact on
ChaiScript?
What is important for defining impact?
Compile Time?
Runtime?
Code Size?
Readability?
I tried to review each of these.

Copyright Jason Turner @le icus 12.2


Which C++ 17 Had The Most Impact on
ChaiScript?
Vote https://strawpoll.com/w767k3kk:
Class template type deduction / guides?
if / switch init expressions?
fold expressions?
if constexpr ?
structured bindings?
std::string_view
std::vector::emplace_back
nested namespace s
noexcept in the type system
Copyright Jason Turner @le icus 12.3
Results Least to Greatest

Copyright Jason Turner @le icus 13.1


noexcept In The Type System

Copyright Jason Turner @le icus 14.1


Impact of noexcept in the type system
Caused a 2x duplication of my function type deduction code
Required conditional compilation with
#if __cpp_noexcept_function_type >= 201510 to get it to compile in
C++17 at all.
This typed support for noexcept appears to give ~1% performance
boost

Copyright Jason Turner @le icus 14.2


Impact of noexcept in the type system
Impact conclusions:
Performance: Possible Minor Improvement
Maintainability: Negative: more overloads necessary, conditional code
Compile Time: None observed
Readability: None
Score: -0.5 necessary but potentially annoying change.

Copyright Jason Turner @le icus 14.3


if-init Expressions

Copyright Jason Turner @le icus 15.1


if-init Expression Impact
I was excited about the possibility of limiting variable scope:
1 auto val = get_some_val();
2 if (val > 5) {
3 // do something else
4 }

becomes
1 if (auto val = get_some_val(); val > 5) {
2 // do something else
3 }

Copyright Jason Turner @le icus 15.2


if-init Expression Impact
But if your code is well modularized and has small functions...
1 bool my_func() {
2 auto val = get_some_val();
3 if (val > 5) {
4 return something(val);
5 } else {
6 return something_else(val);
7 }
8 }

vs
1 bool my_func() {
2 if (auto val = get_some_val(); val > 5) {
3 return something(val);
4 } else {
5 return something_else(val);
6 }
7 }
Copyright Jason Turner @le icus 15.3
if-init Expression Impact
Then how do you format it for readability?
1 bool my_func() {
2 if (auto val = get_some_val(); val > 5) {
3 return something(val);
4 } else {
5 return something_else(val);
6 }
7 }

vs
1 bool my_func() {
2 if (auto val = get_some_val();
3 val > 5) {
4 return something(val);
5 } else {
6 return something_else(val);
7 }
8 }
Copyright Jason Turner @le icus 15.4
if-init Expression Impact
Impact conclusions:
Performance: None
Maintainability: Small
Compile Time: None
Readability: Possibly Hurt
Score: 0.5 disappointing

Copyright Jason Turner @le icus 15.5


Nested Namespaces

Copyright Jason Turner @le icus 16.1


Impact of nested namespaces
1 namespace chaiscript {
2 namespace dispatch {
3 namespace detail {
4 // some thing
5 }
6 // some other thing
7 }
8 }

becomes
1 namespace chaiscript::dispatch {
2 namespace detail {
3 // some thing
4 }
5 // some other thing
6 }

Copyright Jason Turner @le icus 16.2


Impact of nested namespaces
I found these changes quite satisfying to implement.
However:
Causes large whitespace diffs
Can cause large diffs again if you need to insert something in a middle
namespace

Copyright Jason Turner @le icus 16.3


Impact of nested namespaces
Impact conclusions:
Performance: none
Maintainability: possibly negative
Compile time: none
Readability: large
Score: 1.5 surprisingly gratifying to implement. Might make us rethink
some of our coding and formatting standards

Copyright Jason Turner @le icus 16.4


Class Template Type
Deduction

Copyright Jason Turner @le icus 17.1


Class Template Type Deduction Impact
C++17 adds a complete complement of deduction guides for standard
library types.
What impact did this have on my existing code base?
Almost 0

Copyright Jason Turner @le icus 17.2


Class Template Type Deduction Impact
Class template type deduction doesn't help at the class level
1 struct S
2 {
3 // no practical way to use type deduction here
4 std::vector<int> myvec;
5 };

Copyright Jason Turner @le icus 17.3


Class Template Type Deduction Impact
I almost never create local containers, and auto picks up the rest.
Which is better (pretend they do more)?
1 void myfunc() {
2 /// A
3 auto f = [](){ /* something */ };
4 f();
5 }

1 void myfunc() {
2 /// B
3 std::function f = [](){ /* something */ };
4 f();
5 }

Copyright Jason Turner @le icus 17.4


Class Template Type Deduction Impact
Who thinks these are good uses of class template type deduction?
1 std::vector some_strings{
2 "This is a list"s,
3 "of somewhat longish"s,
4 "strings to operate on."s
5 };

1 std::vector some_objs{
2 std::make_unique<Obj>(1),
3 std::make_unique<Obj>(2),
4 std::make_unique<Obj>(3),
5 };

Copyright Jason Turner @le icus 17.5


Class Template Type Deduction Impact
Impact conclusions:
Performance: Possible negative if not considering object lifetime
Maintainability: Small
Compile Time: None
Readability: Small
Score: 1.5

Copyright Jason Turner @le icus 17.6


Structured Bindings

Copyright Jason Turner @le icus 18.1


Impact of structured bindings
Good, helps clean up code.
1 void dump_types() const {
2 for (const auto &v : get_types()) {
3 std::cout << v.first << ": " << v.second->name() << '\n';
4 }
5 }

becomes
1 void dump_types() const {
2 for (const auto &[name, type] : get_types()) {
3 std::cout << name << ": " << type->name() << '\n';
4 }
5 }

Copyright Jason Turner @le icus 18.2


Impact of structured bindings
Is there any performance impact?

Copyright Jason Turner @le icus 18.3


Impact of structured bindings
Which is better?
1 std::string func() {
2 auto val = get_some_val();
3 return val;
4 }

or
1 std::string func() {
2 auto val = get_some_val();
3 return std::move(val);
4 }

Copyright Jason Turner @le icus 18.4


Impact of structured bindings
Why?
1 std::string func() {
2 auto val = get_some_val();
3 return val;
4 }

or
1 std::string func() {
2 auto val = get_some_val();
3 return std::move(val); /// move forced, breaks NRVO
4 }

Copyright Jason Turner @le icus 18.5


Impact of structured bindings
Which is better?
1 std::string func() {
2 auto pair = get_some_pair();
3 return pair.second;
4 }

or
1 std::string func() {
2 auto pair = get_some_pair();
3 return std::move(pair.second);
4 }

Copyright Jason Turner @le icus 18.6


Impact of structured bindings
Why?
1 std::string func() {
2 auto pair = get_some_pair();
3 return pair.second; /// NRVO cannot apply to subobject
4 }

or
1 std::string func() {
2 auto pair = get_some_pair();
3 return std::move(pair.second);
4 }

Copyright Jason Turner @le icus 18.7


Impact of structured bindings
Which is better?
1 std::string func() {
2 auto [succeeded, value] = get_some_pair();
3 return value;
4 }

or
1 std::string func() {
2 auto [succeeded, value] = get_some_pair();
3 return std::move(value);
4 }

Copyright Jason Turner @le icus 18.8


Impact of structured bindings
Why?
1 std::string func() {
2 auto [succeeded, value] = get_some_pair();
3 return value; /// NRVO cannot apply to sub object
4 }

or
1 std::string func() {
2 auto [succeeded, value] = get_some_pair();
3 return std::move(value);
4 }

Copyright Jason Turner @le icus 18.9


Impact of structured bindings
This is (effectively) what the compiler is doing for us
1 std::string func() {
2 auto e = get_some_pair();
3 auto &succeeded = e.first;
4 auto &value = e.second;
5 return value;
6 }

Copyright Jason Turner @le icus 18.10


Impact of structured bindings
Impact conclusions:
Performance: potentially very negative. Now you have to consider the
source of the variable.
Maintainability: Minor
Compile Time: None
Readability: Large
Score: 2 potentially really helpful, but mess with our understand of
object lifetime

Copyright Jason Turner @le icus 18.11


Fold Expressions

Copyright Jason Turner @le icus 19.1


Fold Expression Impact
Almost everything that is possible with fold expressions, is possible with
initializer_lists
1 (void)std::initializer_list<int>{
2 (static_cast<T&>(*this).trace(ds, node), 0)...
3 };

became
1 (static_cast<T&>(*this).trace(ds, node), ... );

Copyright Jason Turner @le icus 19.2


Fold Expression Impact
Everyone agrees both versions have a well defined order, right?
1 (void)std::initializer_list<int>{
2 (static_cast<T&>(*this).trace(ds, node), 0)...
3 };

became
1 (static_cast<T&>(*this).trace(ds, node), ... );

Copyright Jason Turner @le icus 19.3


Fold Expression Impact
Used in this context:
1 void do_trace(const Dispatch_State &ds,
2 const AST_Node_Impl<Tracer<T...>> *node)
3 {
4 (static_cast<T&>(*this).trace(ds, node), ... );
5 }

Copyright Jason Turner @le icus 19.4


Fold Expression Impact
Impact conclusions:
Performance: None
Maintainability: Small
Compile Time: None
Readability: Small
Score 2: no negatives, minor help in some cases

Copyright Jason Turner @le icus 19.5


Emplace Back

Copyright Jason Turner @le icus 20.1


Impact of C++17 emplace_back
This:
1 Boxed_Value &add_get_object(const std::string &t_name,
2 Boxed_Value obj, Stack_Holder &t_holder)
3 {
4 auto &stack_elem = get_stack_data(t_holder).back();
5
6 if (std::any_of(stack_elem.begin(), stack_elem.end(),
7 [&](const std::pair<std::string, Boxed_Value> &o) {
8 return o.first == t_name;
9 }))
10 {
11 throw chaiscript::exception::name_conflict_error(t_name);
12 }
13
14 stack_elem.emplace_back(t_name, std::move(obj))
15 return stack_elem.back().second;
16 }

Copyright Jason Turner @le icus 20.2


Impact of C++17 emplace_back
Became:
1 Boxed_Value &add_get_object(const std::string &t_name,
2 Boxed_Value obj, Stack_Holder &t_holder)
3 {
4 auto &stack_elem = get_stack_data(t_holder).back();
5
6 if (std::any_of(stack_elem.begin(), stack_elem.end(),
7 [&](const std::pair<std::string, Boxed_Value> &o) {
8 return o.first == t_name;
9 }))
10 {
11 throw chaiscript::exception::name_conflict_error(t_name);
12 }
13
14 return stack_elem.emplace_back(t_name, std::move(obj)).second;
15 }

Copyright Jason Turner @le icus 20.3


Impact of C++17 emplace_back
Impact Conclusions:
Performance: tiny possibility of performance improvement (I did
measure some)
Maintainability: minor
Compile time: none
Readability: minor
Score: 2.5 possible performance, readability, and maintainability helps

Copyright Jason Turner @le icus 20.4


std::string_view

Copyright Jason Turner @le icus 21.1


Impact of std::string_view
The main impact of using std::string_view is in looking up of identifiers
and avoiding temporary strings.
But this comes with a complexity cost.

Copyright Jason Turner @le icus 21.2


Impact of std::string_view
Cost of std::string_view :

What does this string_view constructor need to do?


1 std::string_view(const char *);

It must calculate the length of the passed in string. Which might happen
at compile-time or might not.

Copyright Jason Turner @le icus 21.3


Impact of std::string_view
Provided conversions:
std::string automatically converts to std::string_view via conversion
operator
const char * automatically converts to std::string via non- explicit
constructor
const char * automatically converts to std::string_view via
non- explicit constructor
Provided comparisons:
operator<(const std::string &lhs, const std::string &rhs)
operator<(std::string_view lhs, std::string_view rhs)
etc
Copyright Jason Turner @le icus 21.4
Impact of std::string_view
Is this code OK?
1 std::map<std::string_view, int> map;
2
3 void add(const std::string &t_str, int val)
4 {
5 map[t_str] = val;
6 }

Copyright Jason Turner @le icus 21.5


Impact of std::string_view
Bad - possible pointer to temporary stored
1 std::map<std::string_view, int> map;
2
3 void add(const std::string &t_str, int val)
4 {
5 map[t_str] = val; ///
6 }

Copyright Jason Turner @le icus 21.6


Impact of std::string_view
Storing a map of string_view is very risky, and you won't get any compiler
warnings.

Copyright Jason Turner @le icus 21.7


Impact of std::string_view
Does this compile?
1 std::map<std::string, int> map;
2
3 int get(std::string_view t_sv) {
4 return map.at(t_sv);
5 }

Copyright Jason Turner @le icus 21.8


Impact of std::string_view
Does this compile? No.
1 std::map<std::string, int> map;
2
3 int get(std::string_view t_sv) {
4 return map.at(t_sv); // no conversion to std::string
5 }

Copyright Jason Turner @le icus 21.9


Impact of std::string_view
We need to use is_transparent comparitor

Copyright Jason Turner @le icus 21.10


Impact of std::string_view
is_transparent is provided by std::less<>

1 std::map<std::string, int, std::less<>> map;


2
3 int get(std::string_view t_sv) {
4
5
6 if (auto itr = map.find(t_sv); itr != map.end()) {
7 return *itr;
8 }
9 throw std::range_error("Key not found");
10 }

Copyright Jason Turner @le icus 21.11


Impact of std::string_view
is_transparent is provided by std::less<>

1 std::map<std::string, int, std::less<>> map;


2
3 int get(std::string_view t_sv) {
4
5 /// What happens for this find?
6 if (auto itr = map.find(t_sv); itr != map.end()) {
7 return *itr;
8 }
9 throw std::range_error("Key not found");
10 }

Copyright Jason Turner @le icus 21.12


Impact of std::string_view
is_transparent is provided by std::less<>

1 std::map<std::string, int, std::less<>> map;


2
3 int get(std::string_view t_sv) {
4 /// a temporary string_view is need for each comparison
5 /// by using the operator<(string_view, string_view)
6 if (auto itr = map.find(t_sv); itr != map.end()) {
7 return *itr;
8 }
9 throw std::range_error("Key not found");
10 }

Copyright Jason Turner @le icus 21.13


Impact of std::string_view
is_transparent is provided by std::less<>

1 std::map<std::string, int, std::less<>> map;


2
3 int get(std::string_view t_sv) {
4
5 /// How do we do better?
6 if (auto itr = map.find(t_sv); itr != map.end()) {
7 return *itr;
8 }
9 throw std::range_error("Key not found");
10 }

Copyright Jason Turner @le icus 21.14


Impact of std::string_view
std::less<void> looks something like this:
1 struct less {
2 template<typename LHS, typename RHS>
3 constexpr bool operator()(const LHS &lhs, const RHS &rhs) const
4 {
5 return lhs < rhs;
6 }
7 struct is_tansparent{};
8 };

Copyright Jason Turner @le icus 21.15


Impact of std::string_view
My version is:
1 struct str_less {
2 constexpr bool operator()(
3 const std::string &t_lhs, const std::string &t_rhs) const noexcept
4 {
5 return t_lhs < t_rhs;
6 }
7
8 template<typename LHS, typename RHS>
9 constexpr bool operator()(
10 const LHS &t_lhs, const RHS &t_rhs) const noexcept
11 {
12 return std::lexicographical_compare(
13 t_lhs.begin(), t_lhs.end(), t_rhs.begin(), t_rhs.end());
14 }
15 struct is_transparent{};
16 };

Copyright Jason Turner @le icus 21.16


Impact of std::string_view
I'm sure this is highly dependent on compiler and optimization settings.
This was G++7.2 for me.
1 /// using my str_less saved 2% performance
2 /// across ChaiScript
3 std::map<std::string, int, str_less> map;
4
5 int get(std::string_view t_sv) {
6 if (auto itr = map.find(t_sv); itr != map.end()) {
7 return *itr;
8 }
9 throw std::range_error("Key not found");
10 }

Copyright Jason Turner @le icus 21.17


Impact of std::string_view
Also, don't do this:
1 struct S {
2 S(std::string_view sv) : m_string(std::move(sv)) {}
3 std::string m_string;
4 };

Copyright Jason Turner @le icus 21.18


Impact of std::string_view
Or worse:
1 S make_s(std::string s) {
2 return S(std::move(s));
3 }
4
5 struct S {
6 S(std::string_view sv) : m_string(std::move(sv)) {}
7 std::string m_string;
8 };

Copyright Jason Turner @le icus 21.19


Impact of std::string_view
Which can become: (not actually contrived)
1 S make_s(std::string s) {
2 return S(std::move(s));
3 }
4
5 struct S {
6 S(std::string_view sv) : m_string(std::move(sv)) {}
7 std::string m_string;
8 };
9
10 make_s("Hello World");

Copyright Jason Turner @le icus 21.20


Impact of std::string_view
Performance: Possibly large either good or bad
Maintainability: minor: remove_prefix is helpful with parsing
Compile time: minor
Readability: minor (you know that it's only a "view")
Score: 3 can help in virtually every way, but be aware of pitfalls

Copyright Jason Turner @le icus 21.21


Impact of std::string_view
Notes for deployment:
Use consistently when you move to it, otherwise expensive
conversions back and forth
If you know you're going to ultimately need a std::string use
std::string through the whole call chain
Can have huge impact if you have string data that can be constexpr
I'm still seeing buggy / incomplete constexpr implementations
Understand the impact of using string_view with lookup for std::map /
std::set keys

Copyright Jason Turner @le icus 21.22


if constexpr

Copyright Jason Turner @le icus 22.1


if constexpr Impact
This:
1 template<typename Type>
2 auto do_call_impl(Class *o) const
3 -> std::enable_if_t<std::is_pointer_v<Type>, Boxed_Value>
4 {
5 return Handle_Return<Type>
6 ::handle(o->*m_attr);
7 }
8
9 template<typename Type>
10 auto do_call_impl(Class *o) const
11 -> std::enable_if_t<!std::is_pointer_v<Type>, Boxed_Value>
12 {
13 return Handle_Return<std::add_lvalue_reference_t<Type>>
14 ::handle(o->*m_attr);
15 }

Copyright Jason Turner @le icus 22.2


if constexpr Impact
Can become:
1 template<typename Type>
2 auto do_call_impl(Class *o) const
3 {
4 if constexpr(std::is_pointer_v<Type>) {
5 return detail::Handle_Return<Type>
6 ::handle(o->*m_attr);
7 } else {
8 return detail::Handle_Return<std::add_lvalue_reference_t<Type>>
9 ::handle(o->*m_attr);
10 }
11 }

Copyright Jason Turner @le icus 22.3


if constexpr Impact
Which eliminated about 1/2 of my SFINAE usage, but why not 100%?
constructors and other mixed templated / typed overloads

Copyright Jason Turner @le icus 22.4


if constexpr Impact
1 Any(const Any &t_any)
2 : m_data(t_any.empty() ? nullptr : t_any.m_data->clone())
3 { }
4
5 template<typename ValueType,
6 typename = std::enable_if_t<
7 !std::is_same_v<Any, std::decay_t<ValueType>>>>
8 explicit Any(ValueType &&t_value)
9 : m_data(std::make_unique<Data_Impl<std::decay_t<ValueType>>>(
10 std::forward<ValueType>(t_value)))
11 {
12 }

Copyright Jason Turner @le icus 22.5


if constexpr Impact
1 template<Typename T>
2 static std::unique_ptr<Data> make_data(T &&t) {
3 if constexpr (std::is_same_v<Any, std::decay_t<T>>) {
4 if (!t_any.empty()) { return t_any.m_data->clone(); }
5 else { return nullptr; }
6 } else {
7 return make_unique<Data_Impl<std::decay_t<ValueType>>>(
8 std::forward<ValueType>(t_value));
9 }
10 }
11
12 template<typename ValueType> explicit Any(ValueType &&t_value)
13 : m_data(make_data(std::forward<ValueType>(t_value)))
14 { }

What problem (if any) does this version have?

Copyright Jason Turner @le icus 22.6


if constexpr Impact
Covering move construction
1 template<Typename T> std::unique_ptr<Data> make_data(T &&t) {
2 if constexpr (std::is_same_v<Any, std::decay_t<T>>) {
3 if constexpr (std::!is_const_v<decltype(t)>
4 && std::is_rvalue_reference_v<decltype(t)>) {
5 return std::move(t.m_data);
6 } else {
7 if (!t_any.empty()) { return t_any.m_data->clone(); }
8 else { return nullptr; }
9 }
10 } else {
11 return make_unique<Data_Impl<std::decay_t<ValueType>>>
12 (std::forward<ValueType>(t_value))
13 }
14 }
15
16 template<typename ValueType> explicit Any(ValueType &&t_value)
17 : m_data(make_data(std::forward<ValueType>(t_value)))
18 { }

Copyright Jason Turner @le icus 22.7


if constexpr Impact
Now, I don't like SFINAE, but I'm sticking with it for eliminating options
for overload resolution.

Copyright Jason Turner @le icus 22.8


if constexpr Impact
Side note: Boxed_Number
ChaiScript allows for all operators for all possible arithmetic types
While maintaining the proper types, following C++ standards
Example: float * int returns float , uint64_t * int returns uint64_t
This means all possible combinations must be generated at compile
time
if constexprenabled a rewrite of this code which removed about 160
LOC and allowed for more linear flow

Copyright Jason Turner @le icus 22.9


if constexpr Impact
Impact conclusions:
Performance: none
Maintainability: large
Compile Time: minor (for my use cases)
Readability: large
Score: 5 generally very helpful, no real caveats.

Copyright Jason Turner @le icus 22.10


Class Template Deduction
Guides

Copyright Jason Turner @le icus 23.1


Class Template Deduction Guide Impact
While class template type deduction specifically had almost no impact,
deduction guides had a significant impact

Copyright Jason Turner @le icus 23.2


Class Template Deduction Guide Impact
This:
1 template<typename Ret, typename ... Param>
2 Function fun(Ret (*func)(Param...)) {
3 auto fun_call = Fun_Caller<Ret, Param...>(func);
4 return Function(make_shared<Function_Callable_Impl<
5 Ret (Param...), decltype(fun_call)>>(fun_call));
6 }
7
8 template<typename Ret, typename Class, typename ... Param>
9 Function fun(Ret (Class::*t_func)(Param...) const) {
10 auto call = Const_Caller<Ret, Class, Param...>(t_func);
11 return Function(make_shared<Function_Callable_Impl<
12 Ret (const Class &, Param...), decltype(call)>>(call));
13 }

Plus noexcept , non- const etc

Copyright Jason Turner @le icus 23.3


Class Template Deduction Guide Impact
Is replaced with a series of deduction guides:
1 template<typename Ret, typename Class, typename ... Param>
2 Signature(Ret (Class::*f)(Param ...) volatile) ->
3 Signature<Ret, Params<volatile Class &, Param...>, false, true>;
4
5 template<typename Ret, typename Class, typename ... Param>
6 Signature(Ret (Class::*f)(Param ...) volatile noexcept) ->
7 Signature<Ret, Params<volatile Class &, Param...>, true, true>;
8
9 template<typename Ret, typename Class, typename ... Param>
10 Signature(Ret (Class::*f)(Param ...) volatile const) ->
11 Signature<Ret, Params<volatile const Class &, Param...>, false, true>;
12
13 template<typename Ret, typename Class, typename ... Param>
14 Signature(Ret (Class::*f)(Param ...) volatile const noexcept) ->
15 Signature<Ret, Params<volatile const Class &, Param...>, true, true>;

Plus one simple function

Copyright Jason Turner @le icus 23.4


Class Template Deduction Guide Impact
Net result:
no change in LOC
8x better support for C++ functions ( && , & , volatile )
eliminated mostly redundant code.

Copyright Jason Turner @le icus 23.5


Class Template Deduction Guide Impact
Impact conclusions:
Performance: Minor
Maintainability: Large
Compile Time: Minor
Readability: Large
Score: 6 possibility of large help, including performance increases with
rewritten code

Copyright Jason Turner @le icus 23.6


Copyright Jason Turner Conclusions
@le icus

Copyright Jason Turner @le icus 24.1


Conclusions
There are many constexpr changes which we did not address
The little details have the biggest impact
[[maybe_unused]] gets an honorable mention for removing code like
(void)variable
Being able to do and
std::decay_t<Type>
std::is_trivially_destructible_v<Type> instead of
typename std::decay<Type>::type and
std::is_trivially_destructible<Type>::value goes a long way to
making templated code more readable

Copyright Jason Turner @le icus 24.2


Jason Turner
Co-host of CppCast http://cppcast.com
Host of C++ Weekly https://www.youtube.com/c/JasonTurner-le icus
Co-creator of ChaiScript http://chaiscript.com
Curator of http://cppbestpractices.com
Microso MVP for C++ 2015-present

Copyright Jason Turner @le icus 24.3


Jason Turner
Independent and available for training or contracting
http://articles.emptycrate.com/idocpp
http://articles.emptycrate.com/training.html
Next training: 2 days of best practices @ CppCon 2017

Copyright Jason Turner @le icus 24.4

You might also like