Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[C++] Integrate std::span support for flyweight API #1027

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ public class CppGenerator implements CodeGenerator
{
private static final boolean DISABLE_IMPLICIT_COPYING = Boolean.parseBoolean(
System.getProperty("sbe.cpp.disable.implicit.copying", "false"));
private static final boolean DISABLE_RAW_ARRAYS = Boolean.parseBoolean(
System.getProperty("sbe.cpp.disable.raw.arrays", "false"));
private static final String BASE_INDENT = "";
private static final String INDENT = " ";
private static final String TWO_INDENT = INDENT + INDENT;
Expand Down Expand Up @@ -1612,6 +1614,23 @@ private static CharSequence generateArrayFieldNotPresentCondition(final int sinc
sinceVersion);
}

private static CharSequence generateSpanFieldNotPresentCondition(
final int sinceVersion, final String indent, final String cppTypeName)
{
if (0 == sinceVersion)
{
return "";
}

return String.format(
indent + " if (m_actingVersion < %1$d)\n" +
indent + " {\n" +
indent + " return std::span<const %2$s>();\n" +
indent + " }\n\n",
sinceVersion,
cppTypeName);
}

private static CharSequence generateStringNotPresentCondition(final int sinceVersion, final String indent)
{
if (0 == sinceVersion)
Expand Down Expand Up @@ -1681,10 +1700,16 @@ private CharSequence generateFileHeader(
"#if __cplusplus >= 201703L\n" +
"# include <string_view>\n" +
"# define SBE_NODISCARD [[nodiscard]]\n" +
"# define SBE_USE_STRING_VIEW 1\n" +
"#else\n" +
"# define SBE_NODISCARD\n" +
"#endif\n\n" +

"#if __cplusplus >= 202002L\n" +
"# include <span>\n" +
"# define SBE_USE_SPAN 1\n" +
"#endif\n\n" +

"#if !defined(__STDC_LIMIT_MACROS)\n" +
"# define __STDC_LIMIT_MACROS 1\n" +
"#endif\n\n" +
Expand Down Expand Up @@ -2236,12 +2261,38 @@ private void generateArrayProperty(
accessOrderListenerCall);

new Formatter(sb).format("\n" +
indent + " %1$s &put%2$s(const char *const src)%7$s\n" +
indent + " #ifdef SBE_USE_SPAN\n" +
indent + " SBE_NODISCARD std::span<const %5$s> get%1$sAsSpan() const%7$s\n" +
indent + " {\n" +
"%3$s" +
"%6$s" +
indent + " const char *buffer = m_buffer + m_offset + %4$d;\n" +
indent + " return std::span<const %5$s>(reinterpret_cast<const %5$s*>(buffer), %2$d);\n" +
indent + " }\n" +
indent + " #endif\n",
toUpperFirstChar(propertyName),
arrayLength,
generateSpanFieldNotPresentCondition(propertyToken.version(), indent, cppTypeName),
offset,
cppTypeName,
accessOrderListenerCall,
noexceptDeclaration);

new Formatter(sb).format("\n" +
indent + " #ifdef SBE_USE_SPAN\n" +
indent + " template <std::size_t N>\n" +
indent + " %1$s &put%2$s(std::span<const %4$s, N> src)%7$s\n" +
indent + " {\n" +
indent + " static_assert(N <= %5$d, \"array too large for put%2$s\");\n\n" +
"%6$s" +
indent + " std::memcpy(m_buffer + m_offset + %3$d, src, sizeof(%4$s) * %5$d);\n" +
indent + " std::memcpy(m_buffer + m_offset + %3$d, src.data(), sizeof(%4$s) * N);\n" +
indent + " for (std::size_t start = N; start < %5$d; ++start)\n" +
indent + " {\n" +
indent + " m_buffer[m_offset + %3$d + start] = 0;\n" +
indent + " }\n\n" +
indent + " return *this;\n" +
indent + " }\n",
indent + " }\n" +
indent + " #endif\n",
containingClassName,
toUpperFirstChar(propertyName),
offset,
Expand All @@ -2250,6 +2301,79 @@ private void generateArrayProperty(
accessOrderListenerCall,
noexceptDeclaration);

if (encodingToken.encoding().primitiveType() != PrimitiveType.CHAR)
{
new Formatter(sb).format("\n" +
indent + " #ifdef SBE_USE_SPAN\n" +
indent + " %1$s &put%2$s(std::span<const %4$s> src)\n" +
indent + " {\n" +
indent + " const std::size_t srcLength = src.size();\n" +
indent + " if (srcLength > %5$d)\n" +
indent + " {\n" +
indent + " throw std::runtime_error(\"array too large for put%2$s [E106]\");\n" +
indent + " }\n\n" +

"%6$s" +
indent + " std::memcpy(m_buffer + m_offset + %3$d, src.data(), sizeof(%4$s) * srcLength);\n" +
indent + " for (std::size_t start = srcLength; start < %5$d; ++start)\n" +
indent + " {\n" +
indent + " m_buffer[m_offset + %3$d + start] = 0;\n" +
indent + " }\n\n" +
indent + " return *this;\n" +
indent + " }\n" +
indent + " #endif\n",
containingClassName,
toUpperFirstChar(propertyName),
offset,
cppTypeName,
arrayLength,
accessOrderListenerCall);
}

if (!DISABLE_RAW_ARRAYS)
{
new Formatter(sb).format("\n" +
indent + " #ifdef SBE_USE_SPAN\n" +
indent + " template <typename T>\n" +
// If std::span is available, redirect string literals to the std::span-accepting overload,
// where we can do compile-time bounds checking.
indent + " %1$s &put%2$s(T&& src) %7$s requires\n" +
indent + " (std::is_pointer_v<std::remove_reference_t<T>> &&\n" +
indent + " !std::is_array_v<std::remove_reference_t<T>>)\n" +
indent + " #else\n" +
indent + " %1$s &put%2$s(const char *const src)%7$s\n" +
indent + " #endif\n" +
indent + " {\n" +
"%6$s" +
indent + " std::memcpy(m_buffer + m_offset + %3$d, src, sizeof(%4$s) * %5$d);\n" +
indent + " return *this;\n" +
indent + " }\n",
containingClassName,
toUpperFirstChar(propertyName),
offset,
cppTypeName,
arrayLength,
accessOrderListenerCall,
noexceptDeclaration);

}
if (encodingToken.encoding().primitiveType() == PrimitiveType.CHAR)
{
// Resolve ambiguity of string literal arguments, which match both
// std::span<const char, N> and std::string_view overloads.
new Formatter(sb).format("\n" +
indent + " #ifdef SBE_USE_SPAN\n" +
indent + " template <std::size_t N>\n" +
indent + " %1$s &put%2$s(const char (&src)[N])%3$s\n" +
indent + " {\n" +
indent + " return put%2$s(std::span<const char, N>(src));\n" +
indent + " }\n" +
indent + " #endif\n",
containingClassName,
toUpperFirstChar(propertyName),
noexceptDeclaration);
}

if (arrayLength > 1 && arrayLength <= 4)
{
sb.append("\n").append(indent).append(" ")
Expand Down Expand Up @@ -2310,7 +2434,7 @@ private void generateArrayProperty(
generateJsonEscapedStringGetter(sb, encodingToken, indent, propertyName, accessOrderListenerCall);

new Formatter(sb).format("\n" +
indent + " #if __cplusplus >= 201703L\n" +
indent + " #ifdef SBE_USE_STRING_VIEW\n" +
indent + " SBE_NODISCARD std::string_view get%1$sAsStringView() const%6$s\n" +
indent + " {\n" +
"%4$s" +
Expand All @@ -2332,7 +2456,7 @@ private void generateArrayProperty(
noexceptDeclaration);

new Formatter(sb).format("\n" +
indent + " #if __cplusplus >= 201703L\n" +
indent + " #ifdef SBE_USE_STRING_VIEW\n" +
indent + " %1$s &put%2$s(const std::string_view str)\n" +
indent + " {\n" +
indent + " const std::size_t srcLength = str.length();\n" +
Expand Down
13 changes: 12 additions & 1 deletion sbe-tool/src/test/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
sbe_test(DtoTest codecs)
target_compile_features(DtoTest PRIVATE cxx_std_17)
endif()

# Check if the GCC version supports C++20
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "11.0")
target_compile_features(CodeGenTest PRIVATE cxx_std_20)
endif()
endif()

if (CMAKE_CXX_COMPILER_ID STREQUAL "CLang")
Expand All @@ -111,12 +116,18 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "CLang")
sbe_test(DtoTest codecs)
target_compile_features(DtoTest PRIVATE cxx_std_17)
endif()

# Check if CLang version supports C++20
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "10.0")
target_compile_features(CodeGenTest PRIVATE cxx_std_20)
endif()
endif()

if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
# Check if MSVC version supports C++17
# Check if MSVC version supports C++17 / C++20
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "19.14")
sbe_test(DtoTest codecs)
target_compile_options(DtoTest PRIVATE /std:c++17)
target_compile_options(CodeGenTest PRIVATE /std:c++20)
endif()
endif()
62 changes: 62 additions & 0 deletions sbe-tool/src/test/cpp/CodeGenTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1083,3 +1083,65 @@ TEST_F(CodeGenTest, shouldAllowForMultipleIterations)
std::string passFive = walkCar(carDecoder);
EXPECT_EQ(passOne, passFive);
}

#ifdef SBE_USE_STRING_VIEW
TEST_F(CodeGenTest, shouldBeAbleToUseStdStringViewMethods)
{
std::string vehicleCode(VEHICLE_CODE, Car::vehicleCodeLength());

char buffer[BUFFER_LEN] = {};
std::uint64_t baseOffset = MessageHeader::encodedLength();
Car car;
car.wrapForEncode(buffer, baseOffset, sizeof(buffer));
car.putVehicleCode(std::string_view(vehicleCode));

car.sbeRewind();
EXPECT_EQ(car.getVehicleCodeAsStringView(), vehicleCode);
}
#endif

#ifdef SBE_USE_SPAN
TEST_F(CodeGenTest, shouldBeAbleToUseStdSpanViewMethods)
{
char buffer[BUFFER_LEN] = {};

std::uint64_t baseOffset = MessageHeader::encodedLength();
Car car;
car.wrapForEncode(buffer, baseOffset, sizeof(buffer));

{
std::array<std::int32_t, Car::someNumbersLength()> numbers;
for (std::uint64_t i = 0; i < Car::someNumbersLength(); i++)
{
numbers[i] = static_cast<std::int32_t>(i);
}
car.putSomeNumbers(numbers);
}

car.sbeRewind();

{
std::span<const std::int32_t> numbers = car.getSomeNumbersAsSpan();
ASSERT_EQ(numbers.size(), Car::someNumbersLength());
for (std::uint64_t i = 0; i < numbers.size(); i++)
{
EXPECT_EQ(numbers[i], static_cast<std::int32_t>(i));
}
}
}
#endif

#ifdef SBE_USE_SPAN
TEST_F(CodeGenTest, shouldBeAbleToResolveStringLiterals)
{
char buffer[BUFFER_LEN] = {};

std::uint64_t baseOffset = MessageHeader::encodedLength();
Car car;
car.wrapForEncode(buffer, baseOffset, sizeof(buffer));
car.putVehicleCode("ABCDE");

car.sbeRewind();
EXPECT_EQ(car.getVehicleCodeAsStringView(), "ABCDE");
}
#endif
Loading