diff --git a/rosidl_generator_cpp/resource/idl__struct.hpp.em b/rosidl_generator_cpp/resource/idl__struct.hpp.em index b8f985617..18095c388 100644 --- a/rosidl_generator_cpp/resource/idl__struct.hpp.em +++ b/rosidl_generator_cpp/resource/idl__struct.hpp.em @@ -33,6 +33,7 @@ include_directives = set() #include #include +#include "rosidl_runtime_cpp/byte_helpers.hpp" #include "rosidl_runtime_cpp/bounded_vector.hpp" #include "rosidl_runtime_cpp/message_initialization.hpp" diff --git a/rosidl_generator_cpp/resource/msg__struct.hpp.em b/rosidl_generator_cpp/resource/msg__struct.hpp.em index 867b8c624..1f8c4a84f 100644 --- a/rosidl_generator_cpp/resource/msg__struct.hpp.em +++ b/rosidl_generator_cpp/resource/msg__struct.hpp.em @@ -4,7 +4,7 @@ from rosidl_generator_cpp import create_init_alloc_and_member_lists from rosidl_generator_cpp import escape_string from rosidl_generator_cpp import escape_wstring from rosidl_generator_cpp import msg_type_to_cpp -from rosidl_generator_cpp import MSG_TYPE_TO_CPP +from rosidl_generator_cpp import MSG_TYPE_TO_CPP_CONVERSION from rosidl_generator_cpp import generate_zero_string from rosidl_generator_cpp import generate_default_string from rosidl_parser.definition import AbstractNestedType @@ -235,17 +235,19 @@ non_defaulted_zero_initialized_members = [ #endif @[ end if]@ @[ if isinstance(constant.type, AbstractString)]@ - static const @(MSG_TYPE_TO_CPP['string']) @(constant.name); + static const @(MSG_TYPE_TO_CPP_CONVERSION['string']) @(constant.name); @[ elif isinstance(constant.type, AbstractWString)]@ - static const @(MSG_TYPE_TO_CPP['wstring']) @(constant.name); + static const @(MSG_TYPE_TO_CPP_CONVERSION['wstring']) @(constant.name); @[ else]@ - static constexpr @(MSG_TYPE_TO_CPP[constant.type.typename]) @(constant.name) = + static constexpr @(MSG_TYPE_TO_CPP_CONVERSION[constant.type.typename]) @(constant.name) = @[ if isinstance(constant.type, BasicType)]@ -@[ if constant.type.typename in (*INTEGER_TYPES, *CHARACTER_TYPES, BOOLEAN_TYPE, OCTET_TYPE)]@ +@[ if constant.type.typename in (*INTEGER_TYPES, *CHARACTER_TYPES, BOOLEAN_TYPE)]@ @(int(constant.value))@ @[ if constant.type.typename in UNSIGNED_INTEGER_TYPES]@ u@ @[ end if]@ +@[ elif constant.type.typename == OCTET_TYPE]@ + std::byte{@(constant.value)}@ @[ elif constant.type.typename == 'float']@ @(constant.value)f@ @[ else]@ @@ -336,17 +338,17 @@ using @(message.structure.namespaced_type.name) = @[ end if]@ @[ if isinstance(c.type, AbstractString)]@ template -const @(MSG_TYPE_TO_CPP['string']) +const @(MSG_TYPE_TO_CPP_CONVERSION['string']) @(message.structure.namespaced_type.name)_::@(c.name) = "@(escape_string(c.value))"; @[ elif isinstance(c.type, AbstractWString)]@ template -const @(MSG_TYPE_TO_CPP['wstring']) +const @(MSG_TYPE_TO_CPP_CONVERSION['wstring']) @(message.structure.namespaced_type.name)_::@(c.name) = u"@(escape_wstring(c.value))"; @[ else ]@ #if __cplusplus < 201703L // static constexpr member variable definitions are only needed in C++14 and below, deprecated in C++17 template -constexpr @(MSG_TYPE_TO_CPP[c.type.typename]) @(message.structure.namespaced_type.name)_::@(c.name); +constexpr @(MSG_TYPE_TO_CPP_CONVERSION[c.type.typename]) @(message.structure.namespaced_type.name)_::@(c.name); #endif // __cplusplus < 201703L @[ end if]@ @[ if c.name in msvc_common_macros]@ diff --git a/rosidl_generator_cpp/resource/msg__traits.hpp.em b/rosidl_generator_cpp/resource/msg__traits.hpp.em index 45ed1a4c8..d44994951 100644 --- a/rosidl_generator_cpp/resource/msg__traits.hpp.em +++ b/rosidl_generator_cpp/resource/msg__traits.hpp.em @@ -93,8 +93,10 @@ inline void to_flow_style_yaml( { @[ if isinstance(member.type, BasicType)]@ out << "@(member.name): "; -@[ if member.type.typename in ('octet', 'char', 'wchar')]@ +@[ if member.type.typename in ('char', 'wchar')]@ rosidl_generator_traits::character_value_to_yaml(msg.@(member.name), out); +@[ elif member.type.typename == 'octet']@ + rosidl_generator_traits::value_to_yaml(static_cast(msg.@(member.name)), out); @[ else]@ rosidl_generator_traits::value_to_yaml(msg.@(member.name), out); @[ end if]@ @@ -112,8 +114,10 @@ inline void to_flow_style_yaml( size_t pending_items = msg.@(member.name).size(); for (auto item : msg.@(member.name)) { @[ if isinstance(member.type.value_type, BasicType)]@ -@[ if member.type.value_type.typename in ('octet', 'char', 'wchar')]@ +@[ if member.type.value_type.typename in ('char', 'wchar')]@ rosidl_generator_traits::character_value_to_yaml(item, out); +@[ elif member.type.value_type.typename == 'octet']@ + rosidl_generator_traits::value_to_yaml(static_cast(item), out); @[ else]@ rosidl_generator_traits::value_to_yaml(item, out); @[ end if]@ @@ -158,8 +162,10 @@ inline void to_block_style_yaml( } @[ if isinstance(member.type, BasicType)]@ out << "@(member.name): "; -@[ if member.type.typename in ('octet', 'char', 'wchar')]@ +@[ if member.type.typename in ('char', 'wchar')]@ rosidl_generator_traits::character_value_to_yaml(msg.@(member.name), out); +@[ elif member.type.typename == 'octet']@ + rosidl_generator_traits::value_to_yaml(static_cast(msg.@(member.name)), out); @[ else]@ rosidl_generator_traits::value_to_yaml(msg.@(member.name), out); @[ end if]@ @@ -182,8 +188,10 @@ inline void to_block_style_yaml( } @[ if isinstance(member.type.value_type, BasicType)]@ out << "- "; -@[ if member.type.value_type.typename in ('octet', 'char', 'wchar')]@ +@[ if member.type.value_type.typename in ('char', 'wchar')]@ rosidl_generator_traits::character_value_to_yaml(item, out); +@[ elif member.type.value_type.typename == 'octet']@ + rosidl_generator_traits::value_to_yaml(static_cast(item), out); @[ else]@ rosidl_generator_traits::value_to_yaml(item, out); @[ end if]@ diff --git a/rosidl_generator_cpp/rosidl_generator_cpp/__init__.py b/rosidl_generator_cpp/rosidl_generator_cpp/__init__.py index a87092274..dd5ecbf36 100644 --- a/rosidl_generator_cpp/rosidl_generator_cpp/__init__.py +++ b/rosidl_generator_cpp/rosidl_generator_cpp/__init__.py @@ -13,6 +13,7 @@ # limitations under the License. from ast import literal_eval +from copy import copy from typing import List from rosidl_parser.definition import AbstractGenericString @@ -55,7 +56,7 @@ def prefix_with_bom_if_necessary(content: str) -> str: MSG_TYPE_TO_CPP = { 'boolean': 'bool', - 'octet': 'unsigned char', # TODO change to std::byte with C++17 + 'octet': 'std::byte', 'char': 'unsigned char', # TODO change to char8_t with C++20 'wchar': 'char16_t', 'float': 'float', @@ -75,6 +76,9 @@ def prefix_with_bom_if_necessary(content: str) -> str: 'std::allocator_traits::template rebind_alloc>', } +MSG_TYPE_TO_CPP_CONVERSION = copy(MSG_TYPE_TO_CPP) +MSG_TYPE_TO_CPP_CONVERSION['octet'] = 'rosidl_runtime_cpp::ByteConverter' + def msg_type_only_to_cpp(type_): """ @@ -89,11 +93,11 @@ def msg_type_only_to_cpp(type_): if isinstance(type_, AbstractNestedType): type_ = type_.value_type if isinstance(type_, BasicType): - cpp_type = MSG_TYPE_TO_CPP[type_.typename] + cpp_type = MSG_TYPE_TO_CPP_CONVERSION[type_.typename] elif isinstance(type_, AbstractString): - cpp_type = MSG_TYPE_TO_CPP['string'] + cpp_type = MSG_TYPE_TO_CPP_CONVERSION['string'] elif isinstance(type_, AbstractWString): - cpp_type = MSG_TYPE_TO_CPP['wstring'] + cpp_type = MSG_TYPE_TO_CPP_CONVERSION['wstring'] elif isinstance(type_, NamespacedType): typename = '::'.join(type_.namespaced_name()) cpp_type = typename + '_' @@ -196,11 +200,12 @@ def primitive_value_to_cpp(type_, value): if type_.typename == 'boolean': return 'true' if value else 'false' - if type_.typename in [ - 'char', 'octet' - ]: + if type_.typename == 'char': return f'static_cast({value})' + if type_.typename == 'octet': + return f'std::byte{{{value}}}' + if type_.typename in [ 'short', 'unsigned short', 'wchar', diff --git a/rosidl_generator_tests/test/rosidl_generator_cpp/test_interfaces.cpp b/rosidl_generator_tests/test/rosidl_generator_cpp/test_interfaces.cpp index 15bb4b7ef..af58831e0 100644 --- a/rosidl_generator_tests/test/rosidl_generator_cpp/test_interfaces.cpp +++ b/rosidl_generator_tests/test/rosidl_generator_cpp/test_interfaces.cpp @@ -168,7 +168,7 @@ void test_message_basic_types(rosidl_generator_tests::msg::BasicTypes message) #ifdef __linux__ #pragma GCC diagnostic pop #endif - TEST_BASIC_TYPE_FIELD_ASSIGNMENT(message, byte_value, 0, 255) + TEST_BASIC_TYPE_FIELD_ASSIGNMENT(message, byte_value, std::byte{0}, std::byte{255}) TEST_BASIC_TYPE_FIELD_ASSIGNMENT(message, char_value, 0, UINT8_MAX) TEST_BASIC_TYPE_FIELD_ASSIGNMENT(message, float32_value, FLT_MIN, FLT_MAX) TEST_BASIC_TYPE_FIELD_ASSIGNMENT(message, float64_value, DBL_MIN, DBL_MAX) @@ -487,7 +487,7 @@ TEST(Test_messages, constants_assign) { TEST(Test_messages, defaults) { rosidl_generator_tests::msg::Defaults message; TEST_BASIC_TYPE_FIELD_ASSIGNMENT(message, bool_value, true, false); - TEST_BASIC_TYPE_FIELD_ASSIGNMENT(message, byte_value, 50, 255); + TEST_BASIC_TYPE_FIELD_ASSIGNMENT(message, byte_value, std::byte{50}, std::byte{255}); TEST_BASIC_TYPE_FIELD_ASSIGNMENT(message, char_value, 100, UINT8_MAX); TEST_BASIC_TYPE_FIELD_ASSIGNMENT(message, float32_value, 1.125f, FLT_MAX); TEST_BASIC_TYPE_FIELD_ASSIGNMENT(message, float64_value, 1.125, DBL_MAX); diff --git a/rosidl_generator_tests/test/rosidl_generator_cpp/test_msg_builder.cpp b/rosidl_generator_tests/test/rosidl_generator_cpp/test_msg_builder.cpp index 229668c27..3d3763747 100644 --- a/rosidl_generator_tests/test/rosidl_generator_cpp/test_msg_builder.cpp +++ b/rosidl_generator_tests/test/rosidl_generator_cpp/test_msg_builder.cpp @@ -24,7 +24,7 @@ TEST(Test_msg_initialization, build) { ::rosidl_generator_tests::msg::BasicTypes basic = ::rosidl_generator_tests::build<::rosidl_generator_tests::msg::BasicTypes>() .bool_value(true) - .byte_value(5) + .byte_value(std::byte{5}) .char_value(10) .float32_value(0.1125f) .float64_value(0.01125) @@ -57,7 +57,7 @@ TEST(Test_msg_initialization, build) { { rosidl_generator_tests::build() .bool_value(false) - .byte_value(10) + .byte_value(std::byte{10}) .char_value(20) .float32_value(0.225f) .float64_value(0.0225) @@ -90,7 +90,7 @@ TEST(Test_msg_initialization, build) { rosidl_generator_tests::msg::Arrays arrays = rosidl_generator_tests::build() .bool_values({{true, false, true}}) - .byte_values({{5, 10, 5}}) + .byte_values({{std::byte{5}, std::byte{10}, std::byte{5}}}) .char_values({{10, 20, 10}}) .float32_values({{0.1125f, 0.225f, 0.1125f}}) .float64_values({{0.01125, 0.0225, 0.01125}}) @@ -107,7 +107,7 @@ TEST(Test_msg_initialization, build) { .constants_values({{constants, constants, constants}}) .defaults_values({{defaults, defaults, defaults}}) .bool_values_default({{false, true, false}}) - .byte_values_default({{10, 5, 10}}) + .byte_values_default({{std::byte{10}, std::byte{5}, std::byte{10}}}) .char_values_default({{20, 10, 20}}) .float32_values_default({{0.225f, 0.1125f, 0.225f}}) .float64_values_default({{0.0225, 0.01125, 0.0225}}) diff --git a/rosidl_runtime_cpp/include/rosidl_runtime_cpp/byte_helpers.hpp b/rosidl_runtime_cpp/include/rosidl_runtime_cpp/byte_helpers.hpp new file mode 100644 index 000000000..ace76d57c --- /dev/null +++ b/rosidl_runtime_cpp/include/rosidl_runtime_cpp/byte_helpers.hpp @@ -0,0 +1,195 @@ +// Copyright 2026 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include +#include + +#ifndef ROSIDL_RUNTIME_CPP__BYTE_HELPERS_HPP_ +#define ROSIDL_RUNTIME_CPP__BYTE_HELPERS_HPP_ + +namespace rosidl_runtime_cpp +{ + +struct ByteConverter +{ + std::byte value; + + constexpr ByteConverter() noexcept + : value(std::byte{0}) {} + constexpr ByteConverter(std::byte b) noexcept // NOLINT(runtime/explicit) + : value(b) {} + + template>> + [[deprecated("Direct numeric assignment is deprecated; use std::byte")]] + constexpr ByteConverter(T numeric_value) noexcept // NOLINT(runtime/explicit) + : value(static_cast(numeric_value)) {} + + ByteConverter & operator=(std::byte b) noexcept + { + value = b; + return *this; + } + + template>> + [[deprecated("Assignment from numeric is deprecated; use 'std::byte' instead.")]] + ByteConverter & operator=(T c) + { + value = static_cast(c); + return *this; + } + + [[deprecated("Reading as 'unsigned char' is deprecated; use 'std::byte' instead.")]] + constexpr operator unsigned char() const noexcept {return static_cast(value);} + + constexpr operator std::byte() const noexcept {return value;} + + // template conversion to any integral type + template>> + [[deprecated("Using as integral type is deprecated use as std::byte")]] + constexpr operator T() const noexcept {return static_cast(value);} + + constexpr bool operator==(const ByteConverter & other) const noexcept + { + return value == other.value; + } + constexpr bool operator!=(const ByteConverter & other) const noexcept + { + return value != other.value; + } +}; + +// Equality converted +template>> +constexpr bool operator==(const ByteConverter & lhs, T rhs) noexcept +{ + return static_cast(lhs.value) == static_cast(rhs); +} + +template>> +constexpr bool operator==(T lhs, const ByteConverter & rhs) noexcept {return rhs == lhs;} + +template>> +constexpr bool operator!=(const ByteConverter & lhs, T rhs) noexcept {return !(lhs == rhs);} + +template>> +constexpr bool operator!=(T lhs, const ByteConverter & rhs) noexcept {return !(lhs == rhs);} + + +// Converter for Vectors +template> +struct ByteVector : public std::vector +{ + using Base = std::vector; + using Base::Base; + + operator std::vector &() { + return *reinterpret_cast *>(this); + } + + operator const std::vector &() const { + return *reinterpret_cast *>(this); + } + + [[deprecated("Implicit conversion to std::vector is deprecated" + " use as std::vector")]] + operator std::vector &() { + return *reinterpret_cast *>(this); + } + + [[deprecated("Implicit conversion to std::vector is deprecated" + " use as std::vector")]] + operator const std::vector &() const { + return *reinterpret_cast *>(this); + } + + // template conversion to any integral vector + template>> + [[deprecated("Using as template std::vector is deprecated" + " use as std::vector")]] + operator std::vector() const { + std::vector out; + out.reserve(this->size()); + for (auto & b : *this) { + out.push_back(static_cast(b)); + } + return out; + } +}; + +// Converter for Array +template +struct ByteArray : public std::array +{ + using Base = std::array; + + operator std::array &() { + return reinterpret_cast &>(*this); + } + + operator const std::array &() const { + return reinterpret_cast &>(*this); + } + + [[deprecated("Using as std::array is deprecated use as std::array")]] + operator std::array &() { + return reinterpret_cast &>(*this); + } + + [[deprecated("Using as std::array is deprecated use as std::array")]] + operator const std::array &() const { + return reinterpret_cast &>(*this); + } + + // template conversion to any integral array + template>> + [[deprecated("Using as template std::array is deprecated" + " use as std::array")]] + operator std::array() const { + std::array out{}; + for (size_t i = 0; i < N; ++i) { + out[i] = static_cast((*this)[i]); + } + return out; + } +}; + +// Container Conversion equality checks +template|| + std::is_same_v) && + !std::is_integral_v&& !std::is_integral_v + >> +bool operator==(const ContainerA & lhs, const ContainerB & rhs) +{ + if (lhs.size() != rhs.size()) {return false;} + return std::equal(lhs.begin(), lhs.end(), rhs.begin()); +} + +template|| + std::is_same_v) + >> +bool operator!=(const ContainerA & lhs, const ContainerB & rhs) {return !(lhs == rhs);} + +} // namespace rosidl_runtime_cpp + +#endif // ROSIDL_RUNTIME_CPP__BYTE_HELPERS_HPP_ diff --git a/rosidl_runtime_cpp/include/rosidl_runtime_cpp/traits.hpp b/rosidl_runtime_cpp/include/rosidl_runtime_cpp/traits.hpp index e8cee94a3..7a25ae134 100644 --- a/rosidl_runtime_cpp/include/rosidl_runtime_cpp/traits.hpp +++ b/rosidl_runtime_cpp/include/rosidl_runtime_cpp/traits.hpp @@ -38,6 +38,11 @@ inline void character_value_to_yaml(unsigned char value, std::ostream & out) out.flags(flags); } +inline void value_to_yaml(std::byte value, std::ostream & out) +{ + character_value_to_yaml(std::to_integer(value), out); +} + inline void character_value_to_yaml(char16_t value, std::ostream & out) { auto flags = out.flags(); diff --git a/rosidl_typesupport_introspection_cpp/resource/msg__type_support.cpp.em b/rosidl_typesupport_introspection_cpp/resource/msg__type_support.cpp.em index 392fc8394..d070fb5ad 100644 --- a/rosidl_typesupport_introspection_cpp/resource/msg__type_support.cpp.em +++ b/rosidl_typesupport_introspection_cpp/resource/msg__type_support.cpp.em @@ -75,9 +75,9 @@ def is_vector_bool(member): @[for member in message.structure.members]@ @[ if isinstance(member.type, AbstractNestedType)]@ @{ -from rosidl_generator_cpp import MSG_TYPE_TO_CPP +from rosidl_generator_cpp import MSG_TYPE_TO_CPP_CONVERSION if isinstance(member.type.value_type, BasicType): - type_ = MSG_TYPE_TO_CPP[member.type.value_type.typename] + type_ = MSG_TYPE_TO_CPP_CONVERSION[member.type.value_type.typename] elif isinstance(member.type.value_type, AbstractString): type_ = 'std::string' elif isinstance(member.type.value_type, AbstractWString): diff --git a/rosidl_typesupport_introspection_tests/test/introspection_libraries_under_test.hpp b/rosidl_typesupport_introspection_tests/test/introspection_libraries_under_test.hpp index 77b0e3a8c..210e2bdd1 100644 --- a/rosidl_typesupport_introspection_tests/test/introspection_libraries_under_test.hpp +++ b/rosidl_typesupport_introspection_tests/test/introspection_libraries_under_test.hpp @@ -933,7 +933,7 @@ struct Example auto message = std::make_unique(); message->bool_values.push_back(true); - message->byte_values.push_back(0x1B); + message->byte_values.push_back(std::byte{0x1B}); message->char_values.push_back('z'); message->float32_values.push_back(12.34f); message->float64_values.push_back(1.234); @@ -1027,7 +1027,7 @@ struct Example auto message = std::make_unique(); message->bool_values.push_back(true); - message->byte_values.push_back(0x1B); + message->byte_values.push_back(std::byte{0x1B}); message->char_values.push_back('z'); message->float32_values.push_back(12.34f); message->float64_values.push_back(1.234); @@ -1079,7 +1079,7 @@ struct Example using MessageT = rosidl_typesupport_introspection_tests::srv::Arrays::Response; auto message = std::make_unique(); - message->byte_values[1] = 0xAB; + message->byte_values[1] = std::byte{0xAB}; message->char_values[0] = 'b'; message->int8_values[2] = 123; return message; @@ -1107,7 +1107,7 @@ struct Example rosidl_typesupport_introspection_tests::srv::BasicTypes::Response; auto message = std::make_unique(); message->bool_value = true; - message->byte_value = 0xAB; + message->byte_value = std::byte{0xAB}; message->float64_value = -1.234; message->string_value = "bar"; return message;