From e599f7495183fce619eb5152742deba8db3be643 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johel=20Ernesto=20Guerrero=20Pe=C3=B1a?= Date: Sun, 5 Nov 2023 09:41:36 -0400 Subject: [PATCH 1/2] feat(reflect): add `@rule_of_zero` --- source/reflect.h | 119 +++++++++++++++++++++++++++++++--------------- source/reflect.h2 | 49 ++++++++++++++++--- 2 files changed, 123 insertions(+), 45 deletions(-) diff --git a/source/reflect.h b/source/reflect.h index 7ef18c3232..9c58bc0913 100644 --- a/source/reflect.h +++ b/source/reflect.h @@ -36,10 +36,10 @@ class type_declaration; #line 589 "reflect.h2" class alias_declaration; -#line 928 "reflect.h2" +#line 962 "reflect.h2" class value_member_info; -#line 1445 "reflect.h2" +#line 1482 "reflect.h2" } } @@ -621,6 +621,31 @@ auto partially_ordered_value(meta::type_declaration& t) -> void; #line 868 "reflect.h2" //----------------------------------------------------------------------- // +// C.20: If you can avoid defining default operations, do +// +// ##### Reason +// +// It's the simplest and gives the cleanest semantics. +// +// ... +// +// This is known as "the rule of zero". +// +// -- C++ Core Guidelines +// C.20: If you can avoid defining any default operations, do +// +// +//----------------------------------------------------------------------- +// +// rule_of_zero +// +// a type without declared copy/move/destructor functions +// +auto rule_of_zero(meta::type_declaration& t) -> void; + +#line 902 "reflect.h2" +//----------------------------------------------------------------------- +// // "By definition, a `struct` is a `class` in which members // are by default `public`; that is, // @@ -646,7 +671,7 @@ auto partially_ordered_value(meta::type_declaration& t) -> void; // auto cpp2_struct(meta::type_declaration& t) -> void; -#line 911 "reflect.h2" +#line 945 "reflect.h2" //----------------------------------------------------------------------- // // "C enumerations constitute a curiously half-baked concept. ... @@ -676,7 +701,7 @@ auto basic_enum( cpp2::in bitwise ) -> void; -#line 1117 "reflect.h2" +#line 1151 "reflect.h2" //----------------------------------------------------------------------- // // "An enum[...] is a totally ordered value type that stores a @@ -688,7 +713,7 @@ auto basic_enum( // auto cpp2_enum(meta::type_declaration& t) -> void; -#line 1143 "reflect.h2" +#line 1177 "reflect.h2" //----------------------------------------------------------------------- // // "flag_enum expresses an enumeration that stores values @@ -701,7 +726,7 @@ auto cpp2_enum(meta::type_declaration& t) -> void; // auto flag_enum(meta::type_declaration& t) -> void; -#line 1175 "reflect.h2" +#line 1209 "reflect.h2" //----------------------------------------------------------------------- // // "As with void*, programmers should know that unions [...] are @@ -728,14 +753,14 @@ auto flag_enum(meta::type_declaration& t) -> void; auto cpp2_union(meta::type_declaration& t) -> void; -#line 1331 "reflect.h2" +#line 1365 "reflect.h2" //----------------------------------------------------------------------- // // print - output a pretty-printed visualization of t // auto print(cpp2::in t) -> void; -#line 1341 "reflect.h2" +#line 1375 "reflect.h2" //----------------------------------------------------------------------- // // apply_metafunctions @@ -746,7 +771,7 @@ auto print(cpp2::in t) -> void; auto const& error ) -> bool; -#line 1445 "reflect.h2" +#line 1482 "reflect.h2" } } @@ -1081,7 +1106,7 @@ declaration::declaration(declaration const& that) { if ((*this).has_handler() && !(!(has_initializer())) ) { (*this).report_violation(CPP2_CONTRACT_MSG("cannot add an initializer to a function that already has one")); } if ((*this).has_handler() && !(parent_is_type()) ) { (*this).report_violation(CPP2_CONTRACT_MSG("cannot add an initializer to a function that isn't in a type scope")); } - //require( !has_initializer(), + //require( !has_initializer(), // "cannot add an initializer to a function that already has one"); //require( parent_is_type(), // "cannot add an initializer to a function that isn't in a type scope"); @@ -1442,7 +1467,20 @@ auto partially_ordered_value(meta::type_declaration& t) -> void CPP2_UFCS(basic_value)(t); } -#line 893 "reflect.h2" +#line 890 "reflect.h2" +auto rule_of_zero(meta::type_declaration& t) -> void +{ + for ( auto& mf : CPP2_UFCS(get_member_functions)(t) ) + { + CPP2_UFCS(require)(t, !(CPP2_UFCS(is_constructor_with_that)(mf)) + && !(CPP2_UFCS(is_assignment_with_that)(mf)) + && !(CPP2_UFCS(is_destructor)(mf)), + "the rule of zero requires no copy/move/destructor functions"); + } + CPP2_UFCS(disable_member_function_generation)(t); +} + +#line 927 "reflect.h2" auto cpp2_struct(meta::type_declaration& t) -> void { for ( auto& m : CPP2_UFCS(get_members)(t) ) @@ -1457,10 +1495,10 @@ auto cpp2_struct(meta::type_declaration& t) -> void "a struct may not have a user-defined operator="); } } - CPP2_UFCS(disable_member_function_generation)(t); + CPP2_UFCS(rule_of_zero)(t); } -#line 934 "reflect.h2" +#line 968 "reflect.h2" auto basic_enum( meta::type_declaration& t, auto const& nextval, @@ -1485,7 +1523,7 @@ auto basic_enum( { std::string value{"-1"}; -#line 957 "reflect.h2" +#line 991 "reflect.h2" for ( auto const& m : CPP2_UFCS(get_members)(t) ) if ( CPP2_UFCS(is_member_object)(m)) @@ -1523,7 +1561,7 @@ std::string value{"-1"}; } } -#line 993 "reflect.h2" +#line 1027 "reflect.h2" if ((CPP2_UFCS(empty)(enumerators))) { CPP2_UFCS(error)(t, "an enumeration must contain at least one enumerator value"); return ; @@ -1569,7 +1607,7 @@ std::string value{"-1"}; } } -#line 1039 "reflect.h2" +#line 1073 "reflect.h2" // 2. Replace: Erase the contents and replace with modified contents // // Note that most values and functions are declared as '==' compile-time values, i.e. Cpp1 'constexpr' @@ -1617,7 +1655,7 @@ std::string to_string{" to_string: (this) -> std::string = { \n"}; // Provide a 'to_string' function to print enumerator name(s) -#line 1084 "reflect.h2" +#line 1118 "reflect.h2" { if (bitwise) { to_string += " _ret : std::string = \"(\";\n"; @@ -1649,10 +1687,10 @@ std::string to_string{" to_string: (this) -> std::string = { \n"}; CPP2_UFCS(add_member)(t, std::move(to_string)); } } -#line 1114 "reflect.h2" +#line 1148 "reflect.h2" } -#line 1126 "reflect.h2" +#line 1160 "reflect.h2" auto cpp2_enum(meta::type_declaration& t) -> void { // Let basic_enum do its thing, with an incrementing value generator @@ -1669,7 +1707,7 @@ auto cpp2_enum(meta::type_declaration& t) -> void ); } -#line 1153 "reflect.h2" +#line 1187 "reflect.h2" auto flag_enum(meta::type_declaration& t) -> void { // Let basic_enum do its thing, with a power-of-two value generator @@ -1691,7 +1729,7 @@ auto flag_enum(meta::type_declaration& t) -> void ); } -#line 1199 "reflect.h2" +#line 1233 "reflect.h2" auto cpp2_union(meta::type_declaration& t) -> void { std::vector alternatives {}; @@ -1700,7 +1738,7 @@ auto value{0}; // 1. Gather: All the user-written members, and find/compute the max size -#line 1206 "reflect.h2" +#line 1240 "reflect.h2" for ( auto const& m : CPP2_UFCS(get_members)(t) ) { do @@ -1725,7 +1763,7 @@ auto value{0}; } while (false); ++value; } } -#line 1229 "reflect.h2" +#line 1263 "reflect.h2" std::string discriminator_type {}; if (cpp2::cmp_less(CPP2_UFCS(ssize)(alternatives),std::numeric_limits::max())) { discriminator_type = "i8"; @@ -1740,7 +1778,7 @@ auto value{0}; discriminator_type = "i64"; }}} -#line 1244 "reflect.h2" +#line 1278 "reflect.h2" // 2. Replace: Erase the contents and replace with modified contents CPP2_UFCS(remove_marked_members)(t); @@ -1749,12 +1787,12 @@ std::string storage{" _storage: cpp2::aligned_storage t) -> void { std::cout << CPP2_UFCS(print)(t) << "\n"; } -#line 1345 "reflect.h2" +#line 1379 "reflect.h2" [[nodiscard]] auto apply_metafunctions( declaration_node& n, type_declaration& rtype, @@ -1923,6 +1961,9 @@ auto print(cpp2::in t) -> void else {if (name == "partially_ordered_value") { partially_ordered_value(rtype); } + else {if (name == "rule_of_zero") { + rule_of_zero(rtype); + } else {if (name == "struct") { cpp2_struct(rtype); } @@ -1940,9 +1981,9 @@ auto print(cpp2::in t) -> void } else { error("unrecognized metafunction name: " + name); - error("(temporary alpha limitation) currently the supported names are: interface, polymorphic_base, ordered, weakly_ordered, partially_ordered, copyable, basic_value, value, weakly_ordered_value, partially_ordered_value, struct, enum, flag_enum, union, print"); + error("(temporary alpha limitation) currently the supported names are: interface, polymorphic_base, ordered, weakly_ordered, partially_ordered, copyable, basic_value, value, weakly_ordered_value, partially_ordered_value, rule_of_zero, struct, enum, flag_enum, union, print"); return false; - }}}}}}}}}}}}}}} + }}}}}}}}}}}}}}}} if (( !(CPP2_UFCS(empty)(args)) @@ -1957,7 +1998,7 @@ auto print(cpp2::in t) -> void return true; } -#line 1445 "reflect.h2" +#line 1482 "reflect.h2" } } diff --git a/source/reflect.h2 b/source/reflect.h2 index 3d63b3e027..e9ef828d6e 100644 --- a/source/reflect.h2 +++ b/source/reflect.h2 @@ -396,7 +396,7 @@ function_declaration: @copyable type = pre (!has_initializer(), "cannot add an initializer to a function that already has one") pre (parent_is_type(), "cannot add an initializer to a function that isn't in a type scope") = { - //require( !has_initializer(), + //require( !has_initializer(), // "cannot add an initializer to a function that already has one"); //require( parent_is_type(), // "cannot add an initializer to a function that isn't in a type scope"); @@ -485,7 +485,7 @@ type_declaration: @copyable type = -> std::vector = { ret: std::vector = (); - for n*.get_type_scope_declarations(declaration_node::functions) + for n*.get_type_scope_declarations(declaration_node::functions) do (d) { _ = ret.emplace_back( d, this ); } @@ -865,6 +865,40 @@ partially_ordered_value: (inout t: meta::type_declaration) = } +//----------------------------------------------------------------------- +// +// C.20: If you can avoid defining default operations, do +// +// ##### Reason +// +// It's the simplest and gives the cleanest semantics. +// +// ... +// +// This is known as "the rule of zero". +// +// -- C++ Core Guidelines +// C.20: If you can avoid defining any default operations, do +// +// +//----------------------------------------------------------------------- +// +// rule_of_zero +// +// a type without declared copy/move/destructor functions +// +rule_of_zero: (inout t: meta::type_declaration) = +{ + for t.get_member_functions() do (inout mf) + { + t.require( !mf.is_constructor_with_that() + && !mf.is_assignment_with_that() + && !mf.is_destructor(), + "the rule of zero requires no copy/move/destructor functions"); + } + t.disable_member_function_generation(); +} + //----------------------------------------------------------------------- // // "By definition, a `struct` is a `class` in which members @@ -904,7 +938,7 @@ struct: (inout t: meta::type_declaration) = "a struct may not have a user-defined operator="); } } - t.disable_member_function_generation(); + t.rule_of_zero(); } @@ -1258,8 +1292,8 @@ union: (inout t : meta::type_declaration) storage += "), cpp2::max( "; (copy comma: std::string = "") - for alternatives - next comma = ", " + for alternatives + next comma = ", " do (e) { storage += comma + "alignof((e.type)$)"; } @@ -1407,6 +1441,9 @@ apply_metafunctions: ( else if name == "partially_ordered_value" { partially_ordered_value( rtype ); } + else if name == "rule_of_zero" { + rule_of_zero( rtype ); + } else if name == "struct" { cpp2_struct( rtype ); } @@ -1424,7 +1461,7 @@ apply_metafunctions: ( } else { error( "unrecognized metafunction name: " + name ); - error( "(temporary alpha limitation) currently the supported names are: interface, polymorphic_base, ordered, weakly_ordered, partially_ordered, copyable, basic_value, value, weakly_ordered_value, partially_ordered_value, struct, enum, flag_enum, union, print" ); + error( "(temporary alpha limitation) currently the supported names are: interface, polymorphic_base, ordered, weakly_ordered, partially_ordered, copyable, basic_value, value, weakly_ordered_value, partially_ordered_value, rule_of_zero, struct, enum, flag_enum, union, print" ); return false; } From 7f81e296f34ab6066a8970727a35c414849ffd24 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Tue, 9 Jan 2024 13:15:53 -0800 Subject: [PATCH 2/2] Prefix `cpp1_` to emphasize that this is a Cpp1 rule This is about how to lower to Cpp1 code and let the Cpp1 defaults (the Cpp1 compiler-implemented type metafunction) take over Cpp2 already embraces rule-of-zero in a different way/meaning, by having everything be opt-in all the time... if you write absolutely nothing, all you get is a default ctor and dtor --- source/reflect.h | 32 ++++++++++++++++---------------- source/reflect.h2 | 12 ++++++------ 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/source/reflect.h b/source/reflect.h index 9c58bc0913..e277b603a5 100644 --- a/source/reflect.h +++ b/source/reflect.h @@ -637,11 +637,11 @@ auto partially_ordered_value(meta::type_declaration& t) -> void; // //----------------------------------------------------------------------- // -// rule_of_zero +// cpp1_rule_of_zero // // a type without declared copy/move/destructor functions // -auto rule_of_zero(meta::type_declaration& t) -> void; +auto cpp1_rule_of_zero(meta::type_declaration& t) -> void; #line 902 "reflect.h2" //----------------------------------------------------------------------- @@ -843,7 +843,7 @@ namespace meta { static_cast(CPP2_UFCS(emplace_back)((*cpp2::assert_not_null(_1)), s, source_line::category::cpp2)); }}; { -auto newline_pos{CPP2_UFCS(find)(source, '\n')}; +auto newline_pos = CPP2_UFCS(find)(source, '\n'); // First split this string into source_lines // @@ -1468,7 +1468,7 @@ auto partially_ordered_value(meta::type_declaration& t) -> void } #line 890 "reflect.h2" -auto rule_of_zero(meta::type_declaration& t) -> void +auto cpp1_rule_of_zero(meta::type_declaration& t) -> void { for ( auto& mf : CPP2_UFCS(get_member_functions)(t) ) { @@ -1495,7 +1495,7 @@ auto cpp2_struct(meta::type_declaration& t) -> void "a struct may not have a user-defined operator="); } } - CPP2_UFCS(rule_of_zero)(t); + CPP2_UFCS(cpp1_rule_of_zero)(t); } #line 968 "reflect.h2" @@ -1521,7 +1521,7 @@ auto basic_enum( auto found_non_numeric {false}; { -std::string value{"-1"}; +std::string value = "-1"; #line 991 "reflect.h2" for ( @@ -1651,7 +1651,7 @@ std::string value{"-1"}; CPP2_UFCS(add_member)(t, " operator= : (out this, that) == { }"); CPP2_UFCS(add_member)(t, " operator<=> : (this, that) -> std::strong_ordering;"); { -std::string to_string{" to_string: (this) -> std::string = { \n"}; +std::string to_string = " to_string: (this) -> std::string = { \n"; // Provide a 'to_string' function to print enumerator name(s) @@ -1734,7 +1734,7 @@ auto cpp2_union(meta::type_declaration& t) -> void { std::vector alternatives {}; { -auto value{0}; +auto value = 0; // 1. Gather: All the user-written members, and find/compute the max size @@ -1783,14 +1783,14 @@ auto value{0}; CPP2_UFCS(remove_marked_members)(t); { -std::string storage{" _storage: cpp2::aligned_storage(_storage&), _args...); } else { reinterpret_cast<*" + cpp2::to_string(a.type) + ">(_storage&)* = :" + cpp2::to_string(a.type) + " = (_args...); } _discriminator = " + cpp2::to_string(a.value) + "; }\n")); } { -std::string destroy{" private _destroy: (inout this) = {\n"}; +std::string destroy = " private _destroy: (inout this) = {\n"; // Add destroy @@ -1862,7 +1862,7 @@ std::string destroy{" private _destroy: (inout this) = {\n"}; // Add default constructor CPP2_UFCS(add_member)(t, " operator=: (out this) = { }"); { -std::string value_set{""}; +std::string value_set = ""; // Add copy/move construction and assignment @@ -1961,8 +1961,8 @@ auto print(cpp2::in t) -> void else {if (name == "partially_ordered_value") { partially_ordered_value(rtype); } - else {if (name == "rule_of_zero") { - rule_of_zero(rtype); + else {if (name == "cpp1_rule_of_zero") { + cpp1_rule_of_zero(rtype); } else {if (name == "struct") { cpp2_struct(rtype); @@ -1981,7 +1981,7 @@ auto print(cpp2::in t) -> void } else { error("unrecognized metafunction name: " + name); - error("(temporary alpha limitation) currently the supported names are: interface, polymorphic_base, ordered, weakly_ordered, partially_ordered, copyable, basic_value, value, weakly_ordered_value, partially_ordered_value, rule_of_zero, struct, enum, flag_enum, union, print"); + error("(temporary alpha limitation) currently the supported names are: interface, polymorphic_base, ordered, weakly_ordered, partially_ordered, copyable, basic_value, value, weakly_ordered_value, partially_ordered_value, struct, enum, flag_enum, union, cpp1_rule_of_zero, print"); return false; }}}}}}}}}}}}}}}} diff --git a/source/reflect.h2 b/source/reflect.h2 index e9ef828d6e..8c6b061281 100644 --- a/source/reflect.h2 +++ b/source/reflect.h2 @@ -883,11 +883,11 @@ partially_ordered_value: (inout t: meta::type_declaration) = // //----------------------------------------------------------------------- // -// rule_of_zero +// cpp1_rule_of_zero // // a type without declared copy/move/destructor functions // -rule_of_zero: (inout t: meta::type_declaration) = +cpp1_rule_of_zero: (inout t: meta::type_declaration) = { for t.get_member_functions() do (inout mf) { @@ -938,7 +938,7 @@ struct: (inout t: meta::type_declaration) = "a struct may not have a user-defined operator="); } } - t.rule_of_zero(); + t.cpp1_rule_of_zero(); } @@ -1441,8 +1441,8 @@ apply_metafunctions: ( else if name == "partially_ordered_value" { partially_ordered_value( rtype ); } - else if name == "rule_of_zero" { - rule_of_zero( rtype ); + else if name == "cpp1_rule_of_zero" { + cpp1_rule_of_zero( rtype ); } else if name == "struct" { cpp2_struct( rtype ); @@ -1461,7 +1461,7 @@ apply_metafunctions: ( } else { error( "unrecognized metafunction name: " + name ); - error( "(temporary alpha limitation) currently the supported names are: interface, polymorphic_base, ordered, weakly_ordered, partially_ordered, copyable, basic_value, value, weakly_ordered_value, partially_ordered_value, rule_of_zero, struct, enum, flag_enum, union, print" ); + error( "(temporary alpha limitation) currently the supported names are: interface, polymorphic_base, ordered, weakly_ordered, partially_ordered, copyable, basic_value, value, weakly_ordered_value, partially_ordered_value, struct, enum, flag_enum, union, cpp1_rule_of_zero, print" ); return false; }