diff --git a/README.adoc b/README.adoc index 9ccce1d..267433c 100644 --- a/README.adoc +++ b/README.adoc @@ -15496,7 +15496,7 @@ Programs under link:userland/cpp/[] are examples of https://en.wikipedia.org/wik * templates ** link:userland/cpp/template.cpp[]: basic example ** link:userland/cpp/template_class_with_static_member.cpp[]: https://stackoverflow.com/questions/3229883/static-member-initialization-in-a-class-template -** link:userland/cpp/if_constexpr.cpp[]: C++17 `if constexpr` +** link:userland/cpp/if_constexpr.cpp[]: C++17 `if constexpr`: https://stackoverflow.com/questions/12160765/if-else-at-compile-time-in-c/54647315#54647315 * iostream ** link:userland/cpp/copyfmt.cpp[]: `std::copyfmt` restores stream state, see also: https://stackoverflow.com/questions/12560291/set-back-default-floating-point-print-precision-in-c/53673686#53673686 * fstream @@ -15509,6 +15509,26 @@ Programs under link:userland/cpp/[] are examples of https://en.wikipedia.org/wik *** <> contains a benchmark comparison of different c++ containers *** link:userland/cpp/set.cpp[]: `std::set` contains unique keys +[[cpp-initialization-types]] +==== C++ initialization types + +OMG this is hell, understand when primitive variables are initialized or not: + +* https://stackoverflow.com/questions/3127454/how-do-c-class-members-get-initialized-if-i-dont-do-it-explicitly +* https://blog.tartanllama.xyz/initialization-is-bonkers/ + +Intuition: + +* direct initialization: a constructor called explicitly with at least one argument: https://en.cppreference.com/w/cpp/language/direct_initialization +* default initialization: does not initialize primitive types: https://en.cppreference.com/w/cpp/language/default_initialization +* value initialization: maybe initializes primitive types: https://en.cppreference.com/w/cpp/language/value_initialization +* zero initialization: initializes primitive types + +Good rule: + +* initialize every single variable explicitly to prevent the risk of having uninitialized variables due to programmer error (which is easy to get wrong due to insane rules) +* if you don't define your own default constructor, always `= delete` it instead. This prevents the possibility that variables will be assigned twice due to zero initialization + [[cpp-multithreading]] ==== C++ multithreading diff --git a/path_properties.py b/path_properties.py index e1897b0..ffdebb2 100644 --- a/path_properties.py +++ b/path_properties.py @@ -670,6 +670,8 @@ path_properties_tuples = ( }, ), 'count.cpp': {'more_than_1s': True}, + 'initialization_types.cpp': {'cc_flags': + ['-Wno-unused-variable', LF, '-Wno-unused-but-set-variable', LF]}, 'm5ops.cpp': {'allowed_emulators': {'gem5'}}, 'parallel_sort.cpp': {'minimum_gcc_version': (9, 0, 0)}, 'sleep_for.cpp': { diff --git a/userland/cpp/constexpr_func_infinite_loop.cpp b/userland/cpp/constexpr_func_infinite_loop.cpp new file mode 100644 index 0000000..a6375bc --- /dev/null +++ b/userland/cpp/constexpr_func_infinite_loop.cpp @@ -0,0 +1,22 @@ +// https://cirosantilli.com/linux-kernel-module-cheat#cpp + +constexpr int f() { + int i = 0; + while (1) + i += 1; + return i; +} + +constexpr int g() { + return g(); +} + + +int main() { +#if 0 + // GCC 9.2.1. error: ‘constexpr’ loop iteration count exceeds limit of 262144 (use ‘-fconstexpr-loop-limit=’ to increase the limit) + static_assert(f() == 0); + // GCC 9.2.1. error: ‘constexpr’ evaluation depth exceeds maximum of 512 (use ‘-fconstexpr-depth=’ to increase the maximum) + static_assert(g() == 0); +#endif +} diff --git a/userland/cpp/if_constexpr.cpp b/userland/cpp/if_constexpr.cpp index 3e26120..3d9ef68 100644 --- a/userland/cpp/if_constexpr.cpp +++ b/userland/cpp/if_constexpr.cpp @@ -1,23 +1,30 @@ // https://cirosantilli.com/linux-kernel-module-cheat#cpp + #if __cplusplus >= 201703L #include #include -template +template struct MyClass { - int myFunc() { - if constexpr(std::is_integral()) - return 1; - else - return 2; + MyClass() : myVar{0} {} + void modifyIfNotConst() { + if constexpr(!isconst) { + myVar = 1; + } } + T myVar; + static constexpr bool isconst = std::is_const::value; }; #endif int main() { #if __cplusplus >= 201703L - assert(MyClass().myFunc() == 1); - assert(MyClass().myFunc() == 2); + MyClass x; + MyClass y; + x.modifyIfNotConst(); + y.modifyIfNotConst(); + assert(x.myVar == 1); + assert(y.myVar == 0); #endif } diff --git a/userland/cpp/initialization_types.cpp b/userland/cpp/initialization_types.cpp new file mode 100644 index 0000000..9d76f23 --- /dev/null +++ b/userland/cpp/initialization_types.cpp @@ -0,0 +1,199 @@ +// https://cirosantilli.com/linux-kernel-module-cheat#cpp-initialization-types + +#include +#include + +// zero-initialization because has static storage +// just like a static "local" function variable. +int global; + +int main() { + + // First let's list how different initializations are done explicitly in the code. + { + struct C { + int i; + constexpr C() : i(0) {} + constexpr C(int i) : i(i) {} + }; + + // Default-initialization. + { + C i; + C *j = new C; + } + + // Value initialization. + { + C i{}; + C j = C(); + C k = C{}; + C *l = new C(); + C *m = new C{}; + } + + // Direct initialization. + { + C i(1); + C j{1}; + C *k = new C(1); + } + + // Zero initialization + { + static C i; + } + + // Most vexing parse, function declaration! + { + C myfunc(); + } + + // The syntax goes for primitive types. These serve as the basis + // for the recursive definition.. + { + // Default. + int i; + // Following the cases at: + // https://en.cppreference.com/w/cpp/language/default_initialization + // i is POD and is not an array: therefore nothing is done. + //assert(i == ?); + + // Value. + constexpr int j{}; + // Following the cases at: + // https://en.cppreference.com/w/cpp/language/value_initialization + // j is not a class type, and is not an array type. Therefore it is zero initialized. + static_assert(j == 0); + + // Direct. + constexpr int k{1}; + // Following the cases at: + // https://en.cppreference.com/w/cpp/language/direct_initialization + // is not array, not class, not "if T is a non-class type but the source type is a + // class type" whatever that means, and not bool, therefore standard conversion is used + // and the value is set. + static_assert(k == 1); + } + } + + // Now, let's see which implicit initializations are done for each case recursively. + { + { + struct C { int i; }; + static_assert(std::is_default_constructible()); + static_assert(std::is_aggregate()); + + // Default + C a; + // Following the cases at: + // https://en.cppreference.com/w/cpp/language/default_initialization + // - non-POD? no + // - array? no + // - then: nothing is done. + //assert(i == ?); + + // Value + constexpr C b{}; + // Following the cases at: + // https://en.cppreference.com/w/cpp/language/value_initialization + // - class type with no default constructor? no + // - default constructor that is neither user-provided nor deleted: yes + // - zero initialize + // - has a non-trivial default constructor + // - no + assert(b.i == 0); + + // Aggregate + constexpr C c{5}; + static_assert(c.i == 5); + + // Zero + static C d; + assert(d.i == 0); + } + + { + struct C { + int i; + constexpr C() : i(3) {}; + constexpr C(int i) : i(i) {}; + }; + static_assert(!std::is_pod()); + + // Default + constexpr C a; + // Following the cases at: + // https://en.cppreference.com/w/cpp/language/default_initialization + // - non-POD? yes. Therefore: call C(). which initializes i. + static_assert(a.i == 3); + + // Value + constexpr C b{}; + // Following the cases at: + // https://en.cppreference.com/w/cpp/language/value_initialization + // - class type with no default constructor? no + // - user-provided default constructor? yes. Therefore, default initialize the object. + // So we fall on the above case, and the variable does get set. + static_assert(b.i == 3); + + // Direct + constexpr C c{5}; + // Following the cases at: + // https://en.cppreference.com/w/cpp/language/_initialization + // - array type? no + // - class type? yes. Call constructor. + static_assert(c.i == 5); + } + + { + struct C { + int i; + constexpr C(int i) : i(i) {}; + }; + static_assert(!std::is_pod()); + static_assert(!std::is_default_constructible()); + +#if 0 + // Cannot be default initialized if not default constructible. + // error: no matching function + constexpr C a; +#endif + +#if 0 + // Cannot be value initialized if not default constructible. + // error: no matching function + C b{}; +#endif + + // Direct initialize + constexpr C c{5}; + static_assert(!std::is_pod()); + // - class type with no default constructor? yes + } + + //struct A { T t; A() : t() {} }; + //A a; // t is value-initialized + //A a{}; // t is value-initialized + + //struct A { T t; A() : t{} {} }; + //A a; // t is TODO + //A a{}; // + + //struct A { T t; A() {} }; + //A a; // t is TODO + //A a{}; // t is TODO + + //struct A { T t; A() = default }; + //A d; // t is TOAO + //A d{}; // t is TOAO + + //struct A { T t; A() = deleted }; + //A e; // t is TODO + //A e{}; // t is TODO + + //struct A { T t; A(T t) {} }; + //A f; // t is TODO + //A f{}; // t is TODO + } +} diff --git a/userland/cpp/most_vexing_parse.cpp b/userland/cpp/most_vexing_parse.cpp index c9d30ac..0d6e554 100644 --- a/userland/cpp/most_vexing_parse.cpp +++ b/userland/cpp/most_vexing_parse.cpp @@ -1,20 +1,18 @@ // https://cirosantilli.com/linux-kernel-module-cheat#cpp -#include - int main() { struct D { int i; - D() {} - D(int i) : i(i) {} + constexpr D() : i(0) {} + constexpr D(int i) : i(i) {} }; struct C { int i; - C() : i(1) {} - C(int i) : i(i) {} - C(const D& d) : i(d.i) {} + constexpr C() : i(1) {} + constexpr C(int i) : i(i) {} + constexpr C(const D& d) : i(d.i) {} }; // Declares *FUNCTION* called `c` that returns `C` inside function main. @@ -25,7 +23,7 @@ int main() { // Therefore there would be not way for C++ to distinguish between the two, // and still be backwards compatible with C. { - C c(); + constexpr C c(); #if 0 // ERROR: function definition is not possible inside another function. @@ -37,24 +35,24 @@ int main() { // If you want to call a default constructor, use: { - C c; - assert(c.i == 1); + constexpr C c; + static_assert(c.i == 1); } // For non-default constructors, literal arguments disambiguate // things as this syntax could not possibly be a function declaration. { - C c(2); - assert(c.i == 2); + constexpr C c(2); + static_assert(c.i == 2); } // But sometimes even arguments are not enough: here D() // is interpreted as "a function of type `D f()`" { - C c(D(2)); + constexpr C c(D()); #if 0 // error: request for member ‘i’ in ‘c’, which is of non-class type ‘main()::C(main()::D (*)())’ - assert(c.i == 2); + static_assert(c.i == 0); #endif } @@ -63,22 +61,22 @@ int main() { { // Extra parenthesis. { - C c((D(2))); - assert(c.i == 2); + constexpr C c((D(2))); + static_assert(c.i == 2); } // Initialize through assignment. TODO likely guaranteed to be cost-free, // but confirm. { - C c = C((D(2))); - assert(c.i == 2); + constexpr C c = C((D(2))); + static_assert(c.i == 2); } // Initializer list. Only works if there is no initializer_list constructor. // Only works in general if c does not have an ambiguous initializer_list constructor though. { - C c{D(2)}; - assert(c.i == 2); + constexpr C c{D(2)}; + static_assert(c.i == 2); } } }