diff --git a/CMakeLists.txt b/CMakeLists.txt index c81a68bf..424cf6eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,7 +35,7 @@ endif() ############################################################################### # Project # ############################################################################### -project(fastcdr VERSION 2.3.0 LANGUAGES CXX) +project(fastcdr VERSION 3.0.0 LANGUAGES CXX) set(PROJECT_NAME_STYLED "FastCDR") set(PROJECT_NAME_LARGE "Fast CDR") diff --git a/include/fastcdr/Cdr.h b/include/fastcdr/Cdr.h index 126bf2b9..2e9420d4 100644 --- a/include/fastcdr/Cdr.h +++ b/include/fastcdr/Cdr.h @@ -31,6 +31,7 @@ #include "CdrEncoding.hpp" #include "cdr/fixed_size_string.hpp" +#include "detail/container_introspection_helpers.hpp" #include "detail/container_recursive_inspector.hpp" #include "exceptions/BadParamException.h" #include "exceptions/Exception.h" @@ -56,6 +57,18 @@ extern void serialize( Cdr&, const _T&); +template +extern void serialize_array( + Cdr&, + const _T*, + const size_t); + +template +extern void deserialize_array( + Cdr&, + _T*, + const size_t); + template extern void deserialize( Cdr&, @@ -733,23 +746,89 @@ class Cdr * @return Reference to the eprosima::fastcdr::Cdr object. * @exception exception::NotEnoughMemoryException This exception is thrown when trying to serialize a position that exceeds the internal memory size. */ - template + template const*>::value>::type* = nullptr> Cdr& serialize( const std::array<_T, _Size>& array_t) { - if (!is_multi_array_primitive(&array_t)) - { - Cdr::state dheader_state {allocate_xcdrv2_dheader()}; + serialize_array(array_t.data(), array_t.size()); + return *this; + } + + /*! + * @brief This function template serializes an array. + * @param array_t The array that will be serialized in the buffer. + * @return Reference to the eprosima::fastcdr::Cdr object. + * @exception exception::NotEnoughMemoryException This exception is thrown when trying to serialize a position that exceeds the internal memory size. + */ + template const*>::value && + is_complex_array_or_string>::value>::type* = nullptr> + Cdr& serialize( + const std::array<_T, _Size>& array_t) + { + Cdr::state dheader_state {allocate_xcdrv2_dheader()}; + + serialize_array(array_t.data(), array_t.size()); + + set_xcdrv2_dheader(dheader_state); + + return *this; + } + + /*! + * @brief This function template serializes an array. + * @param array_t The array that will be serialized in the buffer. + * @return Reference to the eprosima::fastcdr::Cdr object. + * @exception exception::NotEnoughMemoryException This exception is thrown when trying to serialize a position that exceeds the internal memory size. + */ + template const*>::value && + !is_complex_array_or_string>::value>::type* = nullptr> + Cdr& serialize( + const std::array<_T, _Size>& array_t) + { + Cdr::state dheader_state {allocate_xcdrv2_dheader()}; + + eprosima::fastcdr::serialize_array(*this, array_t.data(), array_t.size()); + + set_xcdrv2_dheader(dheader_state); + + return *this; + } + + /*! + * @brief This function template serializes a sequence of non-primitive. + * @param vector_t The sequence that will be serialized in the buffer. + * @return Reference to the eprosima::fastcdr::Cdr object. + * @exception exception::NotEnoughMemoryException This exception is thrown when trying to serialize a position that exceeds the internal memory size. + */ + template::value && + !std::is_arithmetic<_T>::value && + !is_complex_array_or_string>::value>::type* = nullptr> + Cdr& serialize( + const std::vector<_T>& vector_t) + { + Cdr::state dheader_state {allocate_xcdrv2_dheader()}; - serialize_array(array_t.data(), array_t.size()); + serialize(static_cast(vector_t.size())); - set_xcdrv2_dheader(dheader_state); + try + { + eprosima::fastcdr::serialize_array(*this, vector_t.data(), vector_t.size()); } - else + catch (exception::Exception& ex) { - serialize_array(array_t.data(), array_t.size()); + set_state(dheader_state); + ex.raise(); } + set_xcdrv2_dheader(dheader_state); + return *this; } @@ -759,8 +838,10 @@ class Cdr * @return Reference to the eprosima::fastcdr::Cdr object. * @exception exception::NotEnoughMemoryException This exception is thrown when trying to serialize a position that exceeds the internal memory size. */ - template::value && - !std::is_arithmetic<_T>::value>::type* = nullptr> + template::value && + !std::is_arithmetic<_T>::value && + is_complex_array_or_string>::value>::type* = nullptr> Cdr& serialize( const std::vector<_T>& vector_t) { @@ -789,8 +870,10 @@ class Cdr * @return Reference to the eprosima::fastcdr::Cdr object. * @exception exception::NotEnoughMemoryException This exception is thrown when trying to serialize a position that exceeds the internal memory size. */ - template::value || - std::is_arithmetic<_T>::value>::type* = nullptr> + template::value || + std::is_arithmetic<_T>::value) && + !is_complex_array_or_string>::value>::type* = nullptr> Cdr& serialize( const std::vector<_T>& vector_t) { @@ -1803,11 +1886,29 @@ class Cdr * @return Reference to the eprosima::fastcdr::Cdr object. * @exception exception::NotEnoughMemoryException This exception is thrown when trying to deserialize a position that exceeds the internal memory size. */ - template + template const*>::value>::type* = nullptr> Cdr& deserialize( std::array<_T, _Size>& array_t) { - if (CdrVersion::XCDRv2 == cdr_version_ && !is_multi_array_primitive(&array_t)) + return deserialize_array(array_t.data(), array_t.size()); + } + + /*! + * @brief This function template deserializes an array. + * @param array_t The variable that will store the array read from the buffer. + * @return Reference to the eprosima::fastcdr::Cdr object. + * @exception exception::NotEnoughMemoryException This exception is thrown when trying to deserialize a position that exceeds the internal memory size. + */ + template const*>::value && + is_complex_array_or_string>::value>::type* = nullptr> + Cdr& deserialize( + std::array<_T, _Size>& array_t) + { + if (CdrVersion::XCDRv2 == cdr_version_) { uint32_t dheader {0}; deserialize(dheader); @@ -1833,14 +1934,126 @@ class Cdr return *this; } + /*! + * @brief This function template deserializes an array. + * @param array_t The variable that will store the array read from the buffer. + * @return Reference to the eprosima::fastcdr::Cdr object. + * @exception exception::NotEnoughMemoryException This exception is thrown when trying to deserialize a position that exceeds the internal memory size. + */ + template const*>::value && + !is_complex_array_or_string>::value>::type* = nullptr> + Cdr& deserialize( + std::array<_T, _Size>& array_t) + { + if (CdrVersion::XCDRv2 == cdr_version_) + { + uint32_t dheader {0}; + deserialize(dheader); + + auto offset = offset_; + + eprosima::fastcdr::deserialize_array(*this, array_t.data(), _Size); + + if (offset_ - offset != dheader) + { + throw exception::BadParamException("Member size greater than size specified by DHEADER"); + } + } + else + { + eprosima::fastcdr::deserialize_array(*this, array_t.data(), _Size); + } + + return *this; + } + /*! * @brief This function template deserializes a sequence of non-primitive. * @param vector_t The variable that will store the sequence read from the buffer. * @return Reference to the eprosima::fastcdr::Cdr object. * @exception exception::NotEnoughMemoryException This exception is thrown when trying to deserialize a position that exceeds the internal memory size. */ - template::value && - !std::is_arithmetic<_T>::value>::type* = nullptr> + template::value && + !std::is_arithmetic<_T>::value && + !is_complex_array_or_string>::value>::type* = nullptr> + Cdr& deserialize( + std::vector<_T>& vector_t) + { + uint32_t sequence_length {0}; + + if (CdrVersion::XCDRv2 == cdr_version_) + { + uint32_t dheader {0}; + deserialize(dheader); + + auto offset = offset_; + + deserialize(sequence_length); + + if (0 == sequence_length) + { + vector_t.clear(); + return *this; + } + else + { + vector_t.resize(sequence_length); + } + + eprosima::fastcdr::deserialize_array(*this, vector_t.data(), vector_t.size()); + + if (offset_ - offset != dheader) + { + throw exception::BadParamException("Member size differs from the size specified by DHEADER"); + } + } + else + { + state state_before_error(*this); + + deserialize(sequence_length); + + if (sequence_length == 0) + { + vector_t.clear(); + return *this; + } + + if ((end_ - offset_) < sequence_length) + { + set_state(state_before_error); + throw exception::NotEnoughMemoryException( + exception::NotEnoughMemoryException::NOT_ENOUGH_MEMORY_MESSAGE_DEFAULT); + } + + try + { + vector_t.resize(sequence_length); + eprosima::fastcdr::deserialize_array(*this, vector_t.data(), vector_t.size()); + } + catch (exception::Exception& ex) + { + set_state(state_before_error); + ex.raise(); + } + } + + return *this; + } + + /*! + * @brief This function template deserializes a sequence of non-primitive. + * @param vector_t The variable that will store the sequence read from the buffer. + * @return Reference to the eprosima::fastcdr::Cdr object. + * @exception exception::NotEnoughMemoryException This exception is thrown when trying to deserialize a position that exceeds the internal memory size. + */ + template::value && + !std::is_arithmetic<_T>::value && + is_complex_array_or_string>::value>::type* = nullptr> Cdr& deserialize( std::vector<_T>& vector_t) { @@ -1917,8 +2130,10 @@ class Cdr * @return Reference to the eprosima::fastcdr::Cdr object. * @exception exception::NotEnoughMemoryException This exception is thrown when trying to deserialize a position that exceeds the internal memory size. */ - template::value || - std::is_arithmetic<_T>::value>::type* = nullptr> + template::value || + std::is_arithmetic<_T>::value) && + !is_complex_array_or_string>::value>::type* = nullptr> Cdr& deserialize( std::vector<_T>& vector_t) { diff --git a/include/fastcdr/detail/container_introspection_helpers.hpp b/include/fastcdr/detail/container_introspection_helpers.hpp new file mode 100644 index 00000000..2c8e846d --- /dev/null +++ b/include/fastcdr/detail/container_introspection_helpers.hpp @@ -0,0 +1,103 @@ +// Copyright 2025 Proyectos y Sistemas de Mantenimiento SL (eProsima). +// +// 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. + +#ifndef _FASTCDR_DETAIL_CONTAINER_INTROSPECTION_HELPERS_HPP_ +#define _FASTCDR_DETAIL_CONTAINER_INTROSPECTION_HELPERS_HPP_ + +#include +#include +#include +#include +#include + +#include + +namespace eprosima { +namespace fastcdr { + +/// Helper trait to deduce multi-array of primitives. +/// Primary template — false by default +template +struct static_is_multi_array_primitive : std::false_type {}; + +/// Specialization: base case for arithmetic or enum types +template +struct static_is_multi_array_primitive + : std::integral_constant< + bool, + std::is_arithmetic::value || std::is_enum::value + > {}; + +/// Recursive case: std::array → peel one layer and recurse +template +struct static_is_multi_array_primitive const*> + : static_is_multi_array_primitive {}; + +/// Helper trait to deduce if a type is a string like +template +struct is_string_like : std::false_type {}; + +template<> +struct is_string_like : std::true_type {}; + +template<> +struct is_string_like : std::true_type {}; + +template +struct is_string_like> : std::true_type {}; + +/// Helper trait to deduce if a type is a container type +template +struct is_container : std::false_type {}; + +template +struct is_container> : std::true_type {}; + +template +struct is_container> : std::true_type {}; + +template +struct is_container> : std::true_type {}; + +/// Helper trait to deduce if the type of +// a std::array or std::vector is either +// * non-array like (i.e std::map, for instance) +// * contains nested containers +// * is string like +// Default: always true for non-arrays +template +struct is_complex_array_or_string : std::true_type {}; + +// Specialization for std::array +// Check if T is string-like or complex +template +struct is_complex_array_or_string> + : std::integral_constant< + bool, + is_string_like::value || is_container::value + > {}; + +// Specialization for std::vector +// check if T is string-like or complex +template +struct is_complex_array_or_string> + : std::integral_constant< + bool, + is_string_like::value || is_container::value + > {}; + +} // namespace fastcdr +} // namespace eprosima + +#endif // _FASTCDR_DETAIL_CONTAINER_INTROSPECTION_HELPERS_HPP_ diff --git a/test/cdr/CMakeLists.txt b/test/cdr/CMakeLists.txt index 5d57f840..3992ebcb 100644 --- a/test/cdr/CMakeLists.txt +++ b/test/cdr/CMakeLists.txt @@ -12,6 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. +############################################################################### +# container_introspection tests +############################################################################### +add_executable(ContainerIntrospectionTests container_introspection.cpp) +set_common_compile_options(ContainerIntrospectionTests) +message(STATUS "fastcdr include dir: ${CMAKE_CURRENT_SOURCE_DIR}") +target_include_directories(ContainerIntrospectionTests + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../include) +target_link_libraries(ContainerIntrospectionTests GTest::gtest_main) +gtest_discover_tests(ContainerIntrospectionTests) + ############################################################################### # fixed_size_string tests ############################################################################### diff --git a/test/cdr/container_introspection.cpp b/test/cdr/container_introspection.cpp new file mode 100644 index 00000000..7edbe598 --- /dev/null +++ b/test/cdr/container_introspection.cpp @@ -0,0 +1,112 @@ +// Copyright 2025 Proyectos y Sistemas de Mantenimiento SL (eProsima). +// +// 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 + +using namespace eprosima::fastcdr; + +struct ContainerIntrospectionTests : public ::testing::Test +{ + struct CustomType {}; +}; + +TEST_F(ContainerIntrospectionTests, static_is_multi_array_primitive) +{ + /// Positive tests + static_assert(static_is_multi_array_primitive::value, "int* should be a multi primitive array"); + static_assert(static_is_multi_array_primitive::value, "float* should be a multi primitive array"); + static_assert(static_is_multi_array_primitive::value, "double* should be a multi primitive array"); + static_assert(static_is_multi_array_primitive::value, + "long double* should be a multi primitive array"); + static_assert(static_is_multi_array_primitive::value, "char* should be a multi primitive array"); + static_assert(static_is_multi_array_primitive::value, "bool* should be a multi primitive array"); + static_assert(static_is_multi_array_primitive::value, "uint8_t* should be a multi primitive array"); + static_assert(static_is_multi_array_primitive::value, + "uint16_t* should be a multi primitive array"); + static_assert(static_is_multi_array_primitive::value, + "uint32_t* should be a multi primitive array"); + static_assert(static_is_multi_array_primitive::value, + "uint64_t* should be a multi primitive array"); + static_assert(static_is_multi_array_primitive const*>::value, + "std::array* should be a multi primitive array"); + static_assert(static_is_multi_array_primitive, 3> const*>::value, + "array of arrays should be a multi primitive array"); + + /// Negative tests + static_assert(!static_is_multi_array_primitive::value, + "CustomType* should not be a multi primitive array"); + static_assert(!static_is_multi_array_primitive::value, + "std::string* should not be a multi primitive array"); + static_assert(!static_is_multi_array_primitive const*>::value, + "std::vector* should not be a multi primitive array"); + static_assert(!static_is_multi_array_primitive const*>::value, + "std::map* should not be a multi primitive array"); + static_assert(!static_is_multi_array_primitive, 3> const*>::value, + "array of vectors should be a multi primitive array"); + static_assert(!static_is_multi_array_primitive, 3> const*>::value, + "array of maps should be a multi primitive array"); +} + +TEST_F(ContainerIntrospectionTests, is_complex_array_or_string) +{ + /// Positive tests + static_assert(is_complex_array_or_string, 10u>>::value, + "array of arrays should be a complex array or string"); + static_assert(is_complex_array_or_string, 10u>>::value, + "array of vectors should be a complex array or string"); + static_assert(is_complex_array_or_string, 10u>>::value, + "array of maps should be a complex array or string"); + static_assert(is_complex_array_or_string>::value, + "array of strings should be a complex array or string"); + static_assert(is_complex_array_or_string>::value, + "array of wide strings should be a complex array or string"); + static_assert(is_complex_array_or_string, 10u>>::value, + "array of fixed strings should be a complex array or string"); + + static_assert(is_complex_array_or_string>>::value, + "vector of arrays should be a complex array or string"); + static_assert(is_complex_array_or_string>>::value, + "vector of vectors should be a complex array or string"); + static_assert(is_complex_array_or_string>>::value, + "vector of maps should be a complex array or string"); + static_assert(is_complex_array_or_string>::value, + "vector of strings should be a complex array or string"); + static_assert(is_complex_array_or_string>::value, + "vector of wide strings should be a complex array or string"); + static_assert(is_complex_array_or_string>>::value, + "vector of fixed strings should be a complex array or string"); + + static_assert(is_complex_array_or_string>::value, + "map of ints should be a complex array or string"); + static_assert(is_complex_array_or_string>::value, + "map of float should be a complex array or string"); + static_assert(is_complex_array_or_string>::value, + "map of CustomType should be a complex array or string"); + + /// Negative tests + static_assert(!is_complex_array_or_string>::value, + "array of ints should not be a complex array or string"); + static_assert(!is_complex_array_or_string>::value, + "array of float should not be a complex array or string"); + static_assert(!is_complex_array_or_string>::value, + "array of CustomType should not be a complex array or string"); + + static_assert(!is_complex_array_or_string>::value, + "vector of ints should not be a complex array or string"); + static_assert(!is_complex_array_or_string>::value, + "vector of float should not be a complex array or string"); + static_assert(!is_complex_array_or_string>::value, + "vector of CustomType should not be a complex array or string"); +} diff --git a/test/xcdr/basic_types.cpp b/test/xcdr/basic_types.cpp index d2633259..77ee2fa0 100644 --- a/test/xcdr/basic_types.cpp +++ b/test/xcdr/basic_types.cpp @@ -112,6 +112,16 @@ void serialize( cdr.end_serialize_type(current_status); } +template<> +void serialize_array( + Cdr& cdr, + InnerBasicTypesShortStruct const* array_ptr, + unsigned long array_size) + +{ + cdr.serialize_array(array_ptr, array_size); +} + template<> void deserialize( Cdr& cdr, @@ -157,6 +167,16 @@ void deserialize( }); } +template<> +void deserialize_array( + Cdr& cdr, + InnerBasicTypesShortStruct* array_ptr, + unsigned long array_size) + +{ + cdr.deserialize_array(array_ptr, array_size); +} + } // namespace fastcdr } // namespace eprosima