diff --git a/docs/templates.rst b/docs/templates.rst index b37e9f48..fe177216 100644 --- a/docs/templates.rst +++ b/docs/templates.rst @@ -325,7 +325,7 @@ C++ Filters C++ Use Queries ------------------------------------------------- -.. autofunction:: nunavut.lang.cpp.uses_std_variant +.. autofunction:: nunavut.lang.cpp.uses_variant :noindex: .. autofunction:: nunavut.lang.cpp.uses_cetl :noindex: diff --git a/src/nunavut/jinja/extensions.py b/src/nunavut/jinja/extensions.py index 38acf439..542c9628 100644 --- a/src/nunavut/jinja/extensions.py +++ b/src/nunavut/jinja/extensions.py @@ -96,8 +96,8 @@ def _do_assert( class UseQuery(Extension): """ - Jinja2 extension that allows conditional blocks like ``{% ifuses "std_variant" %}`` or - ``{% ifnuses "std_variant" %}``. These are defined by the :class:`nunavut.lang.Language` object based on the values + Jinja2 extension that allows conditional blocks like ``{% ifuses "variant" %}`` or + ``{% ifnuses "variant" %}``. These are defined by the :class:`nunavut.lang.Language` object based on the values returned from :meth:`nunavut.lang.Language.get_uses_queries`. .. code-block:: python diff --git a/src/nunavut/lang/cpp/__init__.py b/src/nunavut/lang/cpp/__init__.py index 25133244..245b97d6 100644 --- a/src/nunavut/lang/cpp/__init__.py +++ b/src/nunavut/lang/cpp/__init__.py @@ -109,6 +109,18 @@ class SpecialMethod(Enum): """ Move constructor that also takes an allocator argument """ +class CompositeSubType(Enum): + """ + Enum to designate how fields are contained within a composite type + """ + + Structure = auto() + """ Object contains a set of sequential fields """ + + Union = auto() + """ Object contains one field which may hold any value from a set of types """ + + # +-------------------------------------------------------------------------------------------------------------------+ # | LANGUAGE SUPPORT # +-------------------------------------------------------------------------------------------------------------------+ @@ -185,6 +197,7 @@ def _validate_language_options( def _validate_globals(self, globals_map: typing.Dict[str, typing.Any]) -> typing.Dict[str, typing.Any]: globals_map["ConstructorConvention"] = ConstructorConvention globals_map["SpecialMethod"] = SpecialMethod + globals_map["CompositeSubType"] = CompositeSubType return globals_map @staticmethod @@ -294,6 +307,8 @@ def standard_version(self) -> int: @property def has_variant(self) -> bool: """ + Indicates if the language standard and flavor combo provides a type compliant with std::variant + .. invisible-code-block: python from nunavut.lang import LanguageClassLoader @@ -330,7 +345,36 @@ def has_variant(self) -> bool: assert language.has_variant """ - return self.standard_version >= 17 + return (self.standard_version >= 17) or (self.standard_version == 14 and self.standard_flavor == "cetl") + + @property + def right_hand_token(self) -> str: + """ + Symbolic token configured to represent the right hand value in an overloaded operation + + .. invisible-code-block: python + + from nunavut.lang import LanguageClassLoader + + language = ( + LanguageContextBuilder(include_experimental_languages=True) + .set_target_language("cpp") + .set_target_language_configuration_override("options", { "right_hand_token":"rhs"}) + .create() + .get_target_language() + ) + assert "rhs" == language.right_hand_token + + language = ( + LanguageContextBuilder(include_experimental_languages=True) + .set_target_language("cpp") + .set_target_language_configuration_override("options", { "right_hand_token":"other"}) + .create() + .get_target_language() + ) + assert "other" == language.right_hand_token + """ + return str(self.get_option("right_hand_token", "")) def get_includes(self, dep_types: Dependencies) -> typing.List[str]: """ @@ -396,8 +440,6 @@ def do_includes_test(override_vla_include, override_allocator_include): std_includes.append("array") if dep_types.uses_boolean_static_array: std_includes.append("bitset") - if dep_types.uses_union and self.has_variant: - std_includes.append("variant") includes_formatted = [f"<{include}>" for include in sorted(std_includes)] allocator_include = str(self.get_option("allocator_include", "")) @@ -408,6 +450,10 @@ def do_includes_test(override_vla_include, override_allocator_include): variable_array_include = str(self.get_option("variable_array_type_include", "")) if len(variable_array_include) > 0: includes_formatted.append(variable_array_include) + if dep_types.uses_union and uses_variant(self): + variant_include = str(self.get_option("variant_type_include", "")) + if len(variant_include) > 0: + includes_formatted.append(variant_include) return includes_formatted @@ -431,51 +477,65 @@ def create_vla_decl(self, data_type: str, max_size: int) -> str: @template_language_test(__name__) -def uses_std_variant(language: Language) -> bool: +def uses_variant(language: Language) -> bool: """ - Uses query for std variant. + Uses query for variant. + + If the language options contain an entry for C++ and the specified standard or flavor includes a type that is + compliant with ``std::variant``, then this value is true. + + The logic included in this filter can be stated as: + Options has keys [``std``, ``std_flavor``] and the values stored at those keys resolve to one of the following + combinations: + - {``std``: ``>="c++17"``} + - {``std``: ``"c++14"``, ``std_flavor``: ``"cetl"``} - If the language options contain an ``std`` entry for C++ and the specified standard includes the - ``std::variant`` type added to the language at C++17 then this value is true. The logic included - in this filter can be stated as "options has key std and the value for options.std evaluates to - C++ version 17 or greater" but the implementation is able to parse out actual compiler flags like - ``gnu++20`` and is aware of any overrides to suppress use of the standard variant type even if - available. + but the implementation is able to parse out actual compiler flags like ``gnu++20`` and is aware of any overrides + to suppress use of the standard variant type even if available. Example: - .. code-block:: python + .. code-block:: python - template = ''' - {%- ifuses "std_variant" -%} - #include - {%- else -%} - #include "user_variant.h" - {%- endifuses -%} - ''' + template = ''' + {%- ifuses "variant" -%} + #include + {%- else -%} + #include "user_variant.h" + {%- endifuses -%} + ''' - .. invisible-code-block: python + .. invisible-code-block: python - # test c++17 - options = {"std": "c++17"} - lctx = ( - LanguageContextBuilder(include_experimental_languages=True) - .set_target_language("cpp") - .set_target_language_configuration_override(Language.WKCV_LANGUAGE_OPTIONS, options) - .create() - ) - jinja_filter_tester(None, template, '#include ', lctx) + # test c++17 + options = {"std": "c++17"} + lctx = ( + LanguageContextBuilder(include_experimental_languages=True) + .set_target_language("cpp") + .set_target_language_configuration_override(Language.WKCV_LANGUAGE_OPTIONS, options) + .create() + ) + jinja_filter_tester(None, template, '#include ', lctx) - # test c++14 - options = {"std": "c++14"} - lctx = ( - LanguageContextBuilder(include_experimental_languages=True) - .set_target_language("cpp") - .set_target_language_configuration_override(Language.WKCV_LANGUAGE_OPTIONS, options) - .create() - ) - jinja_filter_tester(None, template, '#include "user_variant.h"', lctx) + # test c++14-17 with CETL + options = {"std": "c++14", "std_flavor": "cetl"} + lctx = ( + LanguageContextBuilder(include_experimental_languages=True) + .set_target_language("cpp") + .set_target_language_configuration_override(Language.WKCV_LANGUAGE_OPTIONS, options) + .create() + ) + jinja_filter_tester(None, template, '#include ', lctx) + # test c++14 + options = {"std": "c++14"} + lctx = ( + LanguageContextBuilder(include_experimental_languages=True) + .set_target_language("cpp") + .set_target_language_configuration_override(Language.WKCV_LANGUAGE_OPTIONS, options) + .create() + ) + jinja_filter_tester(None, template, '#include "user_variant.h"', lctx) """ return language.has_variant @@ -1162,12 +1222,17 @@ def needs_rhs(special_method: SpecialMethod) -> bool: def needs_allocator(instance: pydsdl.Any) -> bool: - """Helper method used by filter_value_initializer()""" + """Helper method used by filter_value_initializer() and filter_needs_allocator()""" return isinstance(instance.data_type, pydsdl.VariableLengthArrayType) or isinstance( instance.data_type, pydsdl.CompositeType ) +def needs_variant_init_args(composite_subtype: CompositeSubType) -> bool: + """Helper method used by _prepare_initializer_args()""" + return composite_subtype == CompositeSubType.Union + + def needs_vla_init_args(instance: pydsdl.Any, special_method: SpecialMethod) -> bool: """Helper method used by filter_value_initializer()""" return special_method == SpecialMethod.ALLOCATOR_CONSTRUCTOR and isinstance( @@ -1189,12 +1254,47 @@ def requires_initialization(instance: pydsdl.Any) -> bool: ) -def assemble_initializer_expression( - wrap: str, rhs: str, leading_args: typing.List[str], trailing_args: typing.List[str] -) -> str: +def prepare_initializer_args( + language: Language, + instance: pydsdl.Any, + special_method: SpecialMethod, + composite_subtype: CompositeSubType, +) -> typing.Tuple[typing.List[str], str, typing.List[str]]: + """Prepares a set of arguments for an initialization expression""" + rhs: str = "" + leading_args: typing.List[str] = [] + trailing_args: typing.List[str] = [] + if needs_variant_init_args(composite_subtype): + leading_args.append( + f"{'::'.join(language.support_namespace)}::in_place_index_t{{}}" + ) + + if needs_initializing_value(special_method): + instance_id = language.filter_id(instance) + if needs_rhs(special_method): + rhs = "rhs." + rhs += f"get_{instance_id}()" if composite_subtype is CompositeSubType.Union else instance_id + + if needs_vla_init_args(instance, special_method): + constructor_args = language.get_option("variable_array_type_constructor_args") + if isinstance(constructor_args, str) and len(constructor_args) > 0: + trailing_args.append(constructor_args.format(MAX_SIZE=instance.data_type.capacity)) + + if needs_allocator(instance): + if language.get_option("ctor_convention") == ConstructorConvention.USES_LEADING_ALLOCATOR.value: + leading_args.extend(["std::allocator_arg", "allocator"]) + else: + trailing_args.append("allocator") + + if needs_move(special_method): + rhs = f"std::move({rhs})" + + return (leading_args, rhs, trailing_args) + + +def assemble_initializer_expression(rhs: str, leading_args: typing.List[str], trailing_args: typing.List[str]) -> str: """Helper method used by filter_value_initializer()""" - if wrap: - rhs = f"{wrap}({rhs})" args = [] if rhs: args.append(rhs) @@ -1203,40 +1303,34 @@ def assemble_initializer_expression( @template_language_filter(__name__) -def filter_value_initializer(language: Language, instance: pydsdl.Any, special_method: SpecialMethod) -> str: +def filter_value_initializer( + language: Language, + instance: pydsdl.Any, + special_method: SpecialMethod, + composite_subtype: CompositeSubType = CompositeSubType.Structure, +) -> str: """ Emit an initialization expression for a C++ special method. """ value_initializer: str = "" if requires_initialization(instance): - wrap: str = "" rhs: str = "" leading_args: typing.List[str] = [] trailing_args: typing.List[str] = [] - if needs_initializing_value(special_method): - if needs_rhs(special_method): - rhs = "rhs." - rhs += language.filter_id(instance) - - if needs_vla_init_args(instance, special_method): - constructor_args = language.get_option("variable_array_type_constructor_args") - if isinstance(constructor_args, str) and len(constructor_args) > 0: - trailing_args.append(constructor_args.format(MAX_SIZE=instance.data_type.capacity)) - - if needs_allocator(instance): - if language.get_option("ctor_convention") == ConstructorConvention.USES_LEADING_ALLOCATOR.value: - leading_args.extend(["std::allocator_arg", "allocator"]) - else: - trailing_args.append("allocator") + leading_args, rhs, trailing_args = prepare_initializer_args( + language, instance, special_method, composite_subtype + ) + value_initializer = assemble_initializer_expression(rhs, leading_args, trailing_args) - if needs_move(special_method): - wrap = "std::move" + return value_initializer - value_initializer = assemble_initializer_expression(wrap, rhs, leading_args, trailing_args) - return value_initializer +@template_language_test(__name__) +def filter_needs_allocator(_: Language, instance: pydsdl.Any) -> bool: + """Emit a boolean value for whether the instance's type needs an allocator or not""" + return needs_allocator(instance) @template_language_filter(__name__) diff --git a/src/nunavut/lang/cpp/support/utility.j2 b/src/nunavut/lang/cpp/support/utility.j2 new file mode 100644 index 00000000..6c4bbd86 --- /dev/null +++ b/src/nunavut/lang/cpp/support/utility.j2 @@ -0,0 +1,56 @@ +// +// AUTOGENERATED, DO NOT EDIT. +// +//--------------------------------------------------------------------------------------------------------------------- +// Language Options +{% for key, value in options.items() -%} +// {{ key }}: {{ value }} +{% endfor %} + +#ifndef NUNAVUT_SUPPORT_UTILITIES_HPP_INCLUDED +#define NUNAVUT_SUPPORT_UTILITIES_HPP_INCLUDED + +{% ifuses "variant" -%} +{%- ifuses "cetl" %} +#include +{%- else -%} +#include +{%- endifuses %} +{%- else -%} +#include +{%- endifuses %} + +namespace nunavut +{ +namespace support +{ + +// Value-specialized type for template instantiation +template +{%- ifuses "variant" %} +{%- ifuses "cetl" %} +using in_place_index_t = cetl::pf17::in_place_index_t; +{%- else %} +using in_place_index_t = std::in_place_index_t; +{%- endifuses %} +{%- else -%} +struct in_place_index_t +{ + explicit in_place_index_t() = default; +}; +{%- endifuses %} + +// Type-tagging for template instantiation +template +{%- ifuses "variant" %} +{%- ifuses "cetl" %} +using in_place_type_t = cetl::pf17::in_place_type_t; +{%- else %} +using in_place_type_t = std::in_place_type_t; +{%- endifuses %} +{%- endifuses %} + +} // end namespace support +} // end namespace nunavut + +#endif // NUNAVUT_SUPPORT_UTILITIES_HPP_INCLUDED diff --git a/src/nunavut/lang/cpp/templates/_composite_type.j2 b/src/nunavut/lang/cpp/templates/_composite_type.j2 index 63fbbe71..3850bc44 100644 --- a/src/nunavut/lang/cpp/templates/_composite_type.j2 +++ b/src/nunavut/lang/cpp/templates/_composite_type.j2 @@ -4,13 +4,17 @@ # SPDX-License-Identifier: MIT -#} {%- from '_definitions.j2' import assert -%} -{%- ifuses "std_variant" %} +{%- ifuses "variant" %} +{%- ifuses "cetl" %} + +{% else %} // +-------------------------------------------------------------------------------------------------------------------+ // | This implementation uses the C++17 standard library variant type with wrappers for the emplace and // | get_if methods to support backwards-compatibility with the C++14 version of this object. The union_value type // | extends std::variant and can be used with the entire set of variant methods. Using std::variant directly does mean // | your code will not be backwards compatible with the C++14 version of this object. // +-------------------------------------------------------------------------------------------------------------------+ +{% endifuses -%} {% else %} // +-------------------------------------------------------------------------------------------------------------------+ // | This implementation uses a minimal variant implementation that is forward-compatible with the same types generated @@ -102,20 +106,26 @@ struct {% if composite_type.deprecated -%} // Allocator constructor explicit {{composite_type|short_reference_name}}(const allocator_type& allocator) - {%- if composite_type.fields_except_padding %} :{% endif %} + {%- if composite_type.fields_except_padding %} : {%- if composite_type.inner_type is UnionType %} - union_value{} // can't make use of the allocator with a union + union_value{{ composite_type.fields_except_padding[0] | value_initializer(SpecialMethod.ALLOCATOR_CONSTRUCTOR, CompositeSubType.Union) }} {%- else %} {%- for field in composite_type.fields_except_padding %} {{ field | id }}{{ field | value_initializer(SpecialMethod.ALLOCATOR_CONSTRUCTOR) }}{%if not loop.last %},{%endif %} {%- endfor %} {%- endif %} + {%- endif %} { (void)allocator; // avoid unused param warning } - {%- if composite_type.inner_type is not UnionType %} - {% if composite_type.fields_except_padding %} + {% if composite_type.inner_type is UnionType -%} + // Initializing constructor + template + {{composite_type|short_reference_name}}(nunavut::support::in_place_index_t i, Args&&... args) + : union_value{i, std::forward(args)...} + {} + {%- elif composite_type.fields_except_padding %} // Initializing constructor {{ composite_type | explicit_decorator(SpecialMethod.INITIALIZING_CONSTRUCTOR_WITH_ALLOCATOR)}}( {%- for field in composite_type.fields_except_padding %} @@ -131,23 +141,33 @@ struct {% if composite_type.deprecated -%} (void)allocator; // avoid unused param warning } {%- endif %} - {%- endif %} // Copy constructor {{composite_type|short_reference_name}}(const {{composite_type|short_reference_name}}&) = default; // Copy constructor with allocator - {{composite_type|short_reference_name}}(const {{composite_type|short_reference_name}}& rhs, const allocator_type& allocator) + {{composite_type|short_reference_name}}(const {{composite_type|short_reference_name}}& {{options.right_hand_token}}, const allocator_type& allocator) {%- if composite_type.fields_except_padding %} :{% endif %} {%- if composite_type.inner_type is UnionType %} - union_value{rhs.union_value} // can't make use of the allocator with a union + union_value{std::move( + {%- set ns = namespace(indent = "") %} + {%- for field in composite_type.fields_except_padding %} + {%- if not loop.last %} + {{ns.indent}}{{options.right_hand_token}}.is_{{ field | id }}() ? + {%- set ns.indent = ns.indent ~ "\t" %} + {%- endif %} + {{ns.indent}}VariantType + {{- field | value_initializer(SpecialMethod.COPY_CONSTRUCTOR_WITH_ALLOCATOR, CompositeSubType.Union) }} + {%- if not loop.last %} :{% endif %} + {%- endfor %} + )} {%- else %} {%- for field in composite_type.fields_except_padding %} {{ field | id }}{{ field | value_initializer(SpecialMethod.COPY_CONSTRUCTOR_WITH_ALLOCATOR) }}{%if not loop.last %},{%endif %} {%- endfor %} - {% endif %} + {%- endif %} { - (void)rhs; // avoid unused param warning + (void){{options.right_hand_token}}; // avoid unused param warning (void)allocator; // avoid unused param warning } @@ -155,17 +175,28 @@ struct {% if composite_type.deprecated -%} {{composite_type|short_reference_name}}({{composite_type|short_reference_name}}&&) = default; // Move constructor with allocator - {{composite_type|short_reference_name}}({{composite_type|short_reference_name}}&& rhs, const allocator_type& allocator) + {{composite_type|short_reference_name}}({{composite_type|short_reference_name}}&& {{options.right_hand_token}}, const allocator_type& allocator) {%- if composite_type.fields_except_padding %} :{% endif %} {%- if composite_type.inner_type is UnionType %} - union_value{} // can't make use of the allocator with a union + union_value{std::move( + {%- set ns = namespace(indent = "") %} + {%- for field in composite_type.fields_except_padding %} + {%- if not loop.last %} + {{ns.indent}}{{options.right_hand_token}}.is_{{ field | id }}() ? + {%- set ns.indent = ns.indent ~ "\t" %} + {%- endif %} + {{ns.indent}}VariantType + {{- field | value_initializer(SpecialMethod.MOVE_CONSTRUCTOR_WITH_ALLOCATOR, CompositeSubType.Union) }} + {%- if not loop.last %} :{% endif %} + {%- endfor %} + )} {%- else %} {%- for field in composite_type.fields_except_padding %} {{ field | id }}{{ field | value_initializer(SpecialMethod.MOVE_CONSTRUCTOR_WITH_ALLOCATOR) }}{%if not loop.last %},{%endif %} {%- endfor %} {%- endif %} { - (void)rhs; // avoid unused param warning + (void){{options.right_hand_token}}; // avoid unused param warning (void)allocator; // avoid unused param warning } @@ -177,7 +208,7 @@ struct {% if composite_type.deprecated -%} // Destructor ~{{composite_type|short_reference_name}}() = default; -{%- endif %} +{% endif %} {%- for constant in composite_type.constants %} {% if loop.first %} @@ -189,7 +220,7 @@ struct {% if composite_type.deprecated -%} static constexpr {{ constant.data_type | declaration }} {{ constant.name | id }} = {{ constant | constant_value }}; {%- endfor -%} {%- if composite_type.inner_type is UnionType -%} -{%- ifuses "std_variant" -%} +{%- ifuses "variant" -%} {% include '_fields_as_variant.j2' %} {%- else -%} {% include '_fields_as_union.j2' %} @@ -219,9 +250,10 @@ struct {% if composite_type.deprecated -%} template typename std::add_lvalue_reference<_traits_::TypeOf::{{field.name|id}}>::type set_{{field.name|id}}(Args&&...v){ - return union_value.emplace(v...); + return union_value.emplace(std::forward(v)...); } -{%- endfor %} +{% endfor %} + VariantType union_value; {%- else -%} {% include '_fields.j2' %} {%- endif %} diff --git a/src/nunavut/lang/cpp/templates/_fields_as_union.j2 b/src/nunavut/lang/cpp/templates/_fields_as_union.j2 index 57a6fdf7..572f9437 100644 --- a/src/nunavut/lang/cpp/templates/_fields_as_union.j2 +++ b/src/nunavut/lang/cpp/templates/_fields_as_union.j2 @@ -16,76 +16,112 @@ } internal_union_value_; public: + static const constexpr std::size_t variant_npos = std::numeric_limits::max(); + struct IndexOf final + { + IndexOf() = delete; +{%- for field in composite_type.fields_except_padding %} + static constexpr const std::size_t {{ field.name | id }} = {{ loop.index0 }}U; +{%- endfor %} + }; + + // Alias maintains compatibility with the version of VariantType used in C++17 + using Base = VariantType; + + // Default constructor VariantType() - : tag_(0) - , internal_union_value_() + : tag_{0U} + , internal_union_value_{} { // This is how the C++17 standard library does it; default initialization as the 0th index. emplace<0>(); } - VariantType(const VariantType& rhs) - : tag_(variant_npos) - , internal_union_value_() + // In-place-index constructor + template + VariantType(nunavut::support::in_place_index_t, Args&&... args) + : tag_{I} + , internal_union_value_{} + { + do_emplace(std::forward(args)...); + } + + // In-place-index initializer list constructor + template + VariantType(nunavut::support::in_place_index_t, std::initializer_list il, Args&&... args) + : tag_{I} + , internal_union_value_{} + { + do_emplace(il, std::forward(args)...); + } + + // Copy constructor + VariantType(const VariantType& {{options.right_hand_token}}) + : tag_{variant_npos} + , internal_union_value_{} { {%- for field in composite_type.fields_except_padding %} - {% if not loop.first %}else {% endif %}if(rhs.tag_ == {{ loop.index0 }}) + {% if not loop.first %}else {% endif %}if({{options.right_hand_token}}.tag_ == {{loop.index0}}) { - do_copy<{{ loop.index0 }}>( - *reinterpret_cast::type>(&rhs.internal_union_value_.{{ field.name | id }}) + do_copy<{{loop.index0}}>( + *reinterpret_cast::type>(&{{options.right_hand_token}}.internal_union_value_.{{ field.name | id }}) ); } {%- endfor %} - tag_ = rhs.tag_; + tag_ = {{options.right_hand_token}}.tag_; } - VariantType(VariantType&& rhs) - : tag_(variant_npos) - , internal_union_value_() + // Move constructor + VariantType(VariantType&& {{options.right_hand_token}}) + : tag_{variant_npos} + , internal_union_value_{} { {%- for field in composite_type.fields_except_padding %} - {% if not loop.first %}else {% endif %}if(rhs.tag_ == {{ loop.index0 }}) + {% if not loop.first %}else {% endif %}if({{options.right_hand_token}}.tag_ == {{loop.index0}}) { - do_emplace<{{ loop.index0 }}>( + do_emplace<{{loop.index0}}>( std::forward<{{ field.data_type | declaration }}>( - *reinterpret_cast::type>(&rhs.internal_union_value_.{{ field.name | id }}) + *reinterpret_cast::type>(&{{options.right_hand_token}}.internal_union_value_.{{ field.name | id }}) ) ); } {%- endfor %} - tag_ = rhs.tag_; + tag_ = {{options.right_hand_token}}.tag_; } - VariantType& operator=(const VariantType& rhs) + + // Copy assignment + VariantType& operator=(const VariantType& {{options.right_hand_token}}) { destroy_current(); {%- for field in composite_type.fields_except_padding %} - {% if not loop.first %}else {% endif %}if(rhs.tag_ == {{ loop.index0 }}) + {% if not loop.first %}else {% endif %}if({{options.right_hand_token}}.tag_ == {{loop.index0}}) { - do_copy<{{ loop.index0 }}>( - *reinterpret_cast::type>(&rhs.internal_union_value_.{{ field.name | id }}) + do_copy<{{loop.index0}}>( + *reinterpret_cast::type>(&{{options.right_hand_token}}.internal_union_value_.{{ field.name | id }}) ); } {%- endfor %} - tag_ = rhs.tag_; + tag_ = {{options.right_hand_token}}.tag_; return *this; } - VariantType& operator=(VariantType&& rhs) + // Move assignment + VariantType& operator=(VariantType&& {{options.right_hand_token}}) { destroy_current(); {%- for field in composite_type.fields_except_padding %} - {% if not loop.first %}else {% endif %}if(rhs.tag_ == {{ loop.index0 }}) + {% if not loop.first %}else {% endif %}if({{options.right_hand_token}}.tag_ == {{loop.index0}}) { - do_emplace<{{ loop.index0 }}>( + do_emplace<{{loop.index0}}>( std::forward<{{ field.data_type | declaration }}>( - *reinterpret_cast::type>(&rhs.internal_union_value_.{{ field.name | id }}) + *reinterpret_cast::type>(&{{options.right_hand_token}}.internal_union_value_.{{ field.name | id }}) ) ); } {%- endfor %} - tag_ = rhs.tag_; + tag_ = {{options.right_hand_token}}.tag_; return *this; } @@ -98,19 +134,10 @@ return tag_; } - struct IndexOf final - { - IndexOf() = delete; -{%- for field in composite_type.fields_except_padding %} - static constexpr const std::size_t {{ field.name | id }} = {{ loop.index0 }}U; -{%- endfor %} - }; - static constexpr const std::size_t MAX_INDEX = {{ composite_type.fields_except_padding | length }}U; - template struct alternative; {% for field in composite_type.fields_except_padding %} - template struct alternative<{{ loop.index0 }}U, Types...> + template struct alternative<{{loop.index0}}, Types...> { using type = {{ field.data_type | declaration }}; static constexpr auto pointer = &VariantType::internal_union_t::{{ field.name | id }}; @@ -120,7 +147,7 @@ template typename VariantType::alternative::type& emplace(Args&&... v) { destroy_current(); - typename alternative::type& result = do_emplace(v...); + typename alternative::type& result = do_emplace(std::forward(v)...); tag_ = I; return result; } @@ -171,7 +198,7 @@ void destroy_current() { {%- for field in composite_type.fields_except_padding if field is not PrimitiveType %} - {% if not loop.first %}else {% endif %}if (tag_ == {{ loop.index0 }}) + {% if not loop.first %}else {% endif %}if (tag_ == IndexOf::{{ field | id }}) { reinterpret_cast<{{ field.data_type | declaration }}*>(std::addressof(internal_union_value_.{{ field.name | id }}))->{{ field.data_type | destructor_name }}(); } @@ -179,5 +206,3 @@ } }; - - VariantType union_value; diff --git a/src/nunavut/lang/cpp/templates/_fields_as_variant.j2 b/src/nunavut/lang/cpp/templates/_fields_as_variant.j2 index 94cbde60..8fb0a737 100644 --- a/src/nunavut/lang/cpp/templates/_fields_as_variant.j2 +++ b/src/nunavut/lang/cpp/templates/_fields_as_variant.j2 @@ -3,7 +3,16 @@ # Copyright (C) 2021 OpenCyphal Development Team # This software is distributed under the terms of the MIT License. #} - class VariantType final : public std::variant< + +{%- set ns = namespace(utility="") -%} + +{%- ifuses "cetl" %} + {%- set ns.utility = "cetl::pf17" -%} +{%- else %} + {%- set ns.utility = "std" -%} +{%- endifuses %} + + class VariantType final : public {{ns.utility}}::variant< {%- for field in composite_type.fields_except_padding %} {{ field.doc | block_comment('cpp-doxygen', 8, 120) }} _traits_::TypeOf::{{field.name|id}}{% if not loop.last %},{% endif %} @@ -12,7 +21,15 @@ { public: - static const constexpr std::size_t variant_npos = std::variant_npos; + static const constexpr std::size_t variant_npos = {{ns.utility}}::variant_npos; + + using Base = {{ns.utility}}::variant< +{%- for field in composite_type.fields_except_padding %} + _traits_::TypeOf::{{field.name|id}}{% if not loop.last %},{% endif %} +{%- endfor %} + >; + + using Base::Base; struct IndexOf final { @@ -27,29 +44,27 @@ struct alternative; template - struct alternative> final + struct alternative> final { - using type = typename std::variant_alternative>::type; + using type = typename {{ns.utility}}::variant_alternative>::type; }; template struct alternative final { - using type = std::add_const_t::type>; + using type = std::add_const_t::type>; }; template - static constexpr typename alternative>::type* get_if(std::variant* v) noexcept + static constexpr typename alternative>::type* get_if({{ns.utility}}::variant* v) noexcept { return std::get_if(v); } template - static constexpr const typename alternative>::type* get_if(const std::variant* v) noexcept + static constexpr const typename alternative>::type* get_if(const {{ns.utility}}::variant* v) noexcept { return std::get_if(v); } }; - - VariantType union_value; diff --git a/src/nunavut/lang/cpp/templates/base.j2 b/src/nunavut/lang/cpp/templates/base.j2 index 66988bba..0298a8cc 100644 --- a/src/nunavut/lang/cpp/templates/base.j2 +++ b/src/nunavut/lang/cpp/templates/base.j2 @@ -46,7 +46,7 @@ {{ options | text_table("// ") }} // // Uses Language Features -// Uses std_variant: {%- ifuses "std_variant" -%}yes{%- else -%}no{%- endifuses -%} +// Uses variant: {% ifuses "variant" -%}yes{%- else -%}no{%- endifuses %} // {%- if T.deprecated and options.std | int < 14 %} {#- Courtesy http://patorjk.com/software/taag/#p=display&f=Big&t=DEPRECATED #} diff --git a/src/nunavut/lang/properties.yaml b/src/nunavut/lang/properties.yaml index f42a02a0..2f035712 100644 --- a/src/nunavut/lang/properties.yaml +++ b/src/nunavut/lang/properties.yaml @@ -318,10 +318,12 @@ nunavut.lang.cpp: variable_array_type_include: "" variable_array_type_template: "std::vector<{TYPE}>" variable_array_type_constructor_args: "" + variant_type_include: "" allocator_include: "" allocator_type: "" allocator_is_default_constructible: true ctor_convention: "default" + right_hand_token: "rhs" defaults: cetl++14-17: std: c++14 @@ -329,6 +331,7 @@ nunavut.lang.cpp: variable_array_type_include: '"cetl/variable_length_array.hpp"' variable_array_type_template: "cetl::VariableLengthArray<{TYPE}, {REBIND_ALLOCATOR}>" variable_array_type_constructor_args: "{MAX_SIZE}" + variant_type_include: '"cetl/pf17/variant.hpp"' allocator_include: '"cetl/pf17/sys/memory_resource.hpp"' allocator_type: "cetl::pf17::pmr::polymorphic_allocator" allocator_is_default_constructible: false @@ -339,6 +342,7 @@ nunavut.lang.cpp: variable_array_type_include: "" variable_array_type_template: "std::vector<{TYPE}, {REBIND_ALLOCATOR}>" variable_array_type_constructor_args: "" + variant_type_include: "" allocator_include: "" allocator_type: "std::pmr::polymorphic_allocator" allocator_is_default_constructible: true diff --git a/test/gentest_uses/templates/Any.j2 b/test/gentest_uses/templates/Any.j2 index fc7816b2..099bfcda 100644 --- a/test/gentest_uses/templates/Any.j2 +++ b/test/gentest_uses/templates/Any.j2 @@ -1,16 +1,16 @@ { -{%- ifuses "std_variant" %} - "ifuses_std_variant": true, +{%- ifuses "variant" %} + "ifuses_variant": true, "never": false, -{%- elifnuses "std_variant" %} - "ifuses_std_variant": false, +{%- elifnuses "variant" %} + "ifuses_variant": false, "never": false, {%- else %} "never": true, {%- endifuses -%} -{%- ifnuses "std_variant" %} - "ifnuses_std_variant": true +{%- ifnuses "variant" %} + "ifnuses_variant": true {%- else %} - "ifnuses_std_variant": false + "ifnuses_variant": false {%- endifnuses %} } diff --git a/test/gentest_uses/test_uses.py b/test/gentest_uses/test_uses.py index cbb96746..e329e69b 100644 --- a/test/gentest_uses/test_uses.py +++ b/test/gentest_uses/test_uses.py @@ -41,5 +41,5 @@ def test_ifuses(gen_paths, std, expect_uses_variant): # type: ignore assert json_blob is not None assert not json_blob["never"] - assert json_blob["ifuses_std_variant"] is expect_uses_variant - assert json_blob["ifnuses_std_variant"] is not expect_uses_variant + assert json_blob["ifuses_variant"] is expect_uses_variant + assert json_blob["ifnuses_variant"] is not expect_uses_variant diff --git a/verification/cpp/suite/test_unionant.cpp b/verification/cpp/suite/test_unionant.cpp index 9710d0fb..c6d988ea 100644 --- a/verification/cpp/suite/test_unionant.cpp +++ b/verification/cpp/suite/test_unionant.cpp @@ -66,22 +66,69 @@ TEST(UnionantTests, get_set_lvalue) } } +/** + * Verify that the variant value can be fetched only as the type being held + */ TEST(UnionantTests, get_if_const_variant) { using ValueType = uavcan::_register::Value_1_0; - const uavcan::_register::Value_1_0 a{}; - const uavcan::primitive::array::Integer32_1_0* p = + const ValueType a{}; + + const uavcan::primitive::Empty_1_0* p_empty = + uavcan::_register::Value_1_0::VariantType::get_if(&a.union_value); + const uavcan::primitive::array::Integer32_1_0* p_int32 = uavcan::_register::Value_1_0::VariantType::get_if(&a.union_value); - ASSERT_EQ(nullptr, p); + + ASSERT_NE(nullptr, p_empty); + ASSERT_EQ(nullptr, p_int32); } +/** + * Verify that the variant value can be fetched only at the alternative index for the type being held + */ TEST(UnionantTests, union_with_same_types) { using ValueType = regulated::basics::UnionWithSameTypes_0_1; - ValueType a{}; - std::array* p = + ValueType a{}; + + regulated::basics::Struct__0_1* p_struct1 = + ValueType::VariantType::get_if(&a.union_value); + regulated::basics::Struct__0_1* p_struct2 = + ValueType::VariantType::get_if(&a.union_value); + std::array* p_delimited_fix_le2 = ValueType::VariantType::get_if(&a.union_value); - ASSERT_EQ(nullptr, p); + + ASSERT_NE(nullptr, p_struct1); + ASSERT_EQ(nullptr, p_struct2); + ASSERT_EQ(nullptr, p_delimited_fix_le2); +} + +/** + * Verify the initializing constructor of the VariantType + */ +TEST(UnionantTests, union_value_init_ctor) +{ + using ValueType = uavcan::_register::Value_1_0; + uavcan::primitive::array::Integer32_1_0 v{{1, 2, 3}}; + const ValueType::VariantType a{ + nunavut::support::in_place_index_t{}, + v + }; + + const uavcan::primitive::Empty_1_0* p_empty = + uavcan::_register::Value_1_0::VariantType::get_if(&a); + const uavcan::primitive::array::Integer32_1_0* p_int32 = + uavcan::_register::Value_1_0::VariantType::get_if(&a); + + ASSERT_EQ(nullptr, p_empty); + ASSERT_NE(nullptr, p_int32); + + if (p_int32 != nullptr) + { + ASSERT_EQ(p_int32->value[0], 1); + ASSERT_EQ(p_int32->value[1], 2); + ASSERT_EQ(p_int32->value[2], 3); + } } /**