Department I C Plus Modern and Lucid C

  • Slides: 26
Download presentation
Department I - C Plus Modern and Lucid C++ Advanced for Professional Programmers Part

Department I - C Plus Modern and Lucid C++ Advanced for Professional Programmers Part 8 – Compile-Time Computation 2 Thomas Corbat / Prof. Peter Sommerlad Rapperswil, 23. 02. 2017 HS 2017

Topics n Finish UDL Operators n C++14 Features n Example: Simple Sequences n Example:

Topics n Finish UDL Operators n C++14 Features n Example: Simple Sequences n Example: Multiplication Table 2

User-Defined Literal Operators

User-Defined Literal Operators

Raw UDL Extreme: Ternary Number at Compile-Time (1/2) n Run-time errors for number conversion

Raw UDL Extreme: Ternary Number at Compile-Time (1/2) n Run-time errors for number conversion is bad n There exists a variadic template version of UDL operators n Interpretation of the characters (at compile-time) often requires a variadic class/variable template with specializations template<char. . . Digits> constexpr unsigned long operator"" _ternary() { return _impl: : ternary_value<Digits. . . >; } n We will also need a helper function to get the value of the digit: 3 n constexpr unsigned long three_to(std: : size_t power) { return power ? 3 ull * three_to(power - 1) : 1 ull; } 4

Raw UDL Extreme: Ternary Number at Compile-Time (2/2) template<char. . . Digits> extern unsigned

Raw UDL Extreme: Ternary Number at Compile-Time (2/2) template<char. . . Digits> extern unsigned long ternary_value; template<char. . . Digits> constexpr unsigned long ternary_value<'0', Digits. . . > { ternary_value<Digits. . . > }; template<char. . . Digits> constexpr unsigned long ternary_value<'1', Digits. . . > { 1 * three_to(sizeof. . . (Digits)) + ternary_value<Digits. . . > }; template<char. . . Digits> constexpr unsigned long ternary_value<'2', Digits. . . > { 2 * three_to(sizeof. . . (Digits)) + ternary_value<Digits. . . > }; template<> constexpr unsigned long ternary_value<>{0}; 5

Ternary Number Compile-Time Steps n Example: 120_ternary n 120_ternary -> resolves to ternary_value<'1', '2',

Ternary Number Compile-Time Steps n Example: 120_ternary n 120_ternary -> resolves to ternary_value<'1', '2', '0'> Partial specialization: ternary_value<'1', Digits. . . > Value: 1 * 32 + ternary_value<'2', '0'> Partial specialization: ternary_value<'2', Digits. . . > Value: 2 * 31 + ternary_value<'0'> Partial specialization: ternary_value<'0', Digits. . . > Value: ternary_value<> Partial specialization: parse_ternary<> Value: 0 Value: 6 from 2 * 31 + 0 Value: 15 from 1 * 32 + 6 6

Ternary Number Compile-Time Restrictions n Can we avoid the duplication of the specialization for

Ternary Number Compile-Time Restrictions n Can we avoid the duplication of the specialization for '0', '1' and '2'? constexpr bool is_ternary_digit(char c) { return c == '0' || c == '1' || c == '2'; } constexpr unsigned value_of(char c) { return c - '0'; } template<char D, char. . . Digits> constexpr std: : enable_if_t<is_ternary_digit(D), unsigned long> ternary_value<D, Digits. . . > { value_of(D) * three_to(sizeof. . . (Digits)) + ternary_value<Digits. . . > }; 7

Ternary Number: static_assert n The declaration of value is barely readable; let’s try static_assert

Ternary Number: static_assert n The declaration of value is barely readable; let’s try static_assert n static_assert(cond, msg); n It is a declaration itself and thus cannot be used with variable templates template<char D> constexpr unsigned value_of() { static_assert(is_ternary_digit(D), "Digits of ternary must be 0, 1 or 2"); return D - '0'; } template<char D, char. . . Digits> constexpr unsigned long ternary_value<D, Digits. . . > { value_of<D>() * three_to(sizeof. . . (Digits)) + ternary_value<Digits. . . > }; n Nice error message during compilation n static_assert prevents SFINAE 8

Ternary Number: Outlook to C++20 (maybe) n Upcomming alternative: Concepts n concept keyword n

Ternary Number: Outlook to C++20 (maybe) n Upcomming alternative: Concepts n concept keyword n Concept name used instead of typename template<char D> concept bool Ternary. Digit = is_ternary_digit(D); template<Ternary. Digit D, Ternary. Digit. . . Digits> constexpr unsigned long ternary_value<D, Digits. . . > {. . . }; n Nice compiler messages. . main. cpp: In function 'int main(int, char**)': . . main. cpp: 40: 27: error: cannot call function 'long unsigned int ternary: : operator""_t() [with char. . . Digits = {'1', '4'}]' std: : cout << "14_t: " << 14_t << std: : endl; ^~~~. . main. cpp: 30: 20: note: constraints not satisfied unsigned long operator "" _t() { ^~~~~~~~. . main. cpp: 30: 20: note: in the expansion of 'Ternary. Digit<Digits>. . . '. . main. cpp: 30: 20: note: 'Ternary. Digit<'4'>' was not satisfied 9

Standard Literal Suffixes n Standard suffixes don’t have a leading underscore n Suffix for

Standard Literal Suffixes n Standard suffixes don’t have a leading underscore n Suffix for std: : string: s n Suffix for std: : complex (imaginary): i, il, if n Suffixes for std: : chrono: : duration: ns, us, ms, s, min, h n More might be defined in the future n Is the following example a problem? using namespace std: : string_literals; using namespace std: : chrono_literals; auto one_s = 1 s; auto one_point_zero_s = 1. 0 s; auto fourty_two_s = "42"s; 10

Summary UDL n User-defined Literals can help… n … to get dimensions of constants

Summary UDL n User-defined Literals can help… n … to get dimensions of constants right n … as a short-hand for type conversions n … to implement compile-time constants of literal types n … obfuscate program code if applied mercilessly n If constexpr, user-defined literals are resolved at compile-time n No runtime-overhead n Don’t specify suffixes for all your types now! 11

C++14 Features

C++14 Features

Compile-Time C++03 Computation 13 Recursion template <size_t n> struct fact { static size_t const

Compile-Time C++03 Computation 13 Recursion template <size_t n> struct fact { static size_t const value{ (n > 1) ? n * fact<n - 1>: : value : 1}; }; template <> struct fact<0> { static size_t const value = 1; }; Base case specialization n C++03 allowed static const member variables to depend on template parameters n So why not make such variable definitions templates without the class scaffolding? n Hidden benefit: compilers memorize already instantiated templates and reuse instantiation n within the limits of a compilation unit

C++14 Variable Templates template <size_t n> constexpr size_t fact { n * fact<n -

C++14 Variable Templates template <size_t n> constexpr size_t fact { n * fact<n - 1> }; template <> constexpr size_t fact<0> { 1 }; n constexpr variable templates define compile-time constants n Can be initialized through recursion, with template-instantiation base case needed or through constexpr functions n An overflow = compile error n If the overflow would result in undefined behavior (e. g. signed integer overflow) 14

C++14 Variable Templates n Are always compile-time constants n Template instantiation computes value n

C++14 Variable Templates n Are always compile-time constants n Template instantiation computes value n Depending on template argument(s) n Variable type can be all that can be compile-time computed n aka a literal type n no memory, addresses etc. available template<typename T> constexpr T pi = T(3. 1415926535897932384626433 L); constexpr auto pint = pi<int>; 15

C++14 constexpr Function Rules n C++14 constexpr functions can. . . have local variables

C++14 constexpr Function Rules n C++14 constexpr functions can. . . have local variables of “literal” type n. . . use loops, recursion, arrays, references n. . . even contain branches that rely on run-time features, if branch is not executed during compiletime computations, e. g. , throw n. . . but can only call constexpr functions n. . . execute almost all kinds of C++ statements that do NOT use run-time features, such as memory allocation or exceptions 16

C++14 Compile-Time Computation n “Simpler and more flexible” than C++11 constexpr functions n Possibly

C++14 Compile-Time Computation n “Simpler and more flexible” than C++11 constexpr functions n Possibly huge compile-time effect and easy to hit compiler-limits n Calculation in context of compiler instead of the target platform! constexpr auto factorial(unsigned n) { Side-effects on local variables only unsigned long res{1}; while (n > 1) { res *= n--; Constant expression context } return res; } static_assert(fac<20> == factorial(20), "loop version incorrect"); 17

C++14 constexpr Compile Times constexpr function members fabricate values, “literal”. a constexpr operator used

C++14 constexpr Compile Times constexpr function members fabricate values, “literal”. a constexpr operator used for user-defined literal by a compiler implicitly called recurses and returns type ‘literalled' compiling permits sword fights for developers all. 18

C++14 Can Deduce return Types n You will write for most constexpr functions or

C++14 Can Deduce return Types n You will write for most constexpr functions or function templates n constexpr auto as return type n They have to be inline anyway, so return type deduction will work template <typename T> constexpr auto abs(T x){ return x < 0 ? –x : x; } static_assert(5 == abs(-5), "abs is correct for negative int"); static_assert(5 == abs(5), "abs is correct for int"); static_assert(5. 0 == abs(-5. 0), "abs is correct for negative double"); static_assert(5. 0 L == abs(-5. 0 L), "abs is correct for negative long double"); 19

Constexpr Functions at Run-Time and Error Detection n Some constexpr functions can only be

Constexpr Functions at Run-Time and Error Detection n Some constexpr functions can only be implemented in a very inefficient way n Calling them at run-time with run-time arguments creates unnecessary overhead n How can one prohibit calling constexpr functions at run-time? n Some constexpr functions might have a narrow contract n Not all argument values are valid (fac(-1)) n Should be compile error as well as a run-time error 20

Compile- and Run-Time Errors for Invalid Arguments n Use throw for both cases n

Compile- and Run-Time Errors for Invalid Arguments n Use throw for both cases n Will result in a compile error and a run-time error #include <stdexcept> #include <iostream> constexpr int fac(int i) { if (i > 0) return i * fac(i - 1); else if (i == 0) return 1; else throw std: : invalid_argument{"negative"}; } int main() { char s[fac(6)]; // check compile time execution std: : cout << "sizeof s = "<< sizeof(s); fac(-1); // run-time error constexpr auto val = fac(-2); // compile time error constexpr auto val 1 = fac(13); // overflow-> compile time error } 21

Run-Time Error Output #include <stdexcept> #include <iostream> constexpr int fac(int i) { if (i

Run-Time Error Output #include <stdexcept> #include <iostream> constexpr int fac(int i) { if (i > 0) return i * fac(i - 1); else if (i == 0) return 1; else throw std: : invalid_argument{"negative"}; } int main() { fac(-1); // run-time error } terminate called after throwing an instance of 'std: : invalid_argument' what(): negative 22

Compile-Time Exception Output #include <stdexcept> #include <iostream> constexpr int fac(int i) { if (i

Compile-Time Exception Output #include <stdexcept> #include <iostream> constexpr int fac(int i) { if (i > 0) return i * fac(i - 1); else if (i == 0) return 1; else throw std: : invalid_argument{"negative"}; } int main() { constexpr auto val = fac(-2); // compile-time error } . . main. cpp: In function 'int main()': . . main. cpp: 14: 27: in constexpr expansion of 'fac(-2)'. . main. cpp: 7: 46: error: expression '<throw-expression>' is not a constantexpression else throw std: : invalid_argument{"negative"}; 23

Compile-Time Overflow Error #include <stdexcept> #include <iostream> constexpr int fac(int i) { if (i

Compile-Time Overflow Error #include <stdexcept> #include <iostream> constexpr int fac(int i) { if (i > 0) return i * fac(i - 1); else if (i == 0) return 1; else throw std: : invalid_argument{"negative"}; } int main() { constexpr auto val 1 = fac(13); // overflow -> compile time error } . . main. cpp: In function 'int main()': . . main. cpp: 15: 28: in constexpr expansion of 'fac(13)'. . main. cpp: 15: 31: error: overflow in constant expression [-fpermissive] constexpr auto val 1 = fac(13); // overflow -> compile time error 24

Compile Error for Run-Time Usage of constexpr Function n Declare an undefined variable identifier

Compile Error for Run-Time Usage of constexpr Function n Declare an undefined variable identifier n Use it in a throw expression n throw should not be eliminated by the optimizer n Generates a linker error if used at run-time constexpr int fac(int i) { // undefined symbol extern char const * const cannot_link_me; 25 Hack if (i > 0) return i * fac(i - 1); else if (i == 0) return 1; // will raise linker error else throw cannot_link_me; } int main() { // check compile time execution constexpr auto val = fac(3); char s[fac(6)]; std: : cout << "sizeof s = "<< sizeof(s); // will raise linker error about undefined symbol fac(5); } main. o: main. cpp: (. rdata$. refptr. cannot_link_me[. refptr. cannot_link_me]+0 x 0): undefined reference to `cannot_link_me' collect 2. exe: error: ld returned 1 exit status

Keyword extern 26 n Tells the compiler that there exists a variable called value

Keyword extern 26 n Tells the compiler that there exists a variable called value of type int somewhere n The compiler does not need to worry about allocating the memory for that variable Global Variable extern int value; n It is the linker's job to find the actual variable in the object files One Definition Rule n Variable is shared (globally) n Opposed to declaring a variable (without extern) n The compiler will allocate memory for the variable n Beware of the one definition rule! I. e. does not make sense in a header file. int value;