Add moveable_types=forward_setter option for perfect forwarding setters
Adds `forward_setter` value to `moveable_types` option, generating perfect forwarding setters for complex types while preserving traditional setters for primitives. Also fixes missing `operator<` implementation that caused link errors when structs are used as map keys.
**Forward Setter Generation** (`compiler/cpp/src/thrift/generate/t_cpp_generator.cc`):
- Parse `moveable_types=forward_setter` option
- Complex types (strings, containers, structs) → template setters with `std::forward<T_>`
- Primitive types → traditional const-ref setters
- Template implementations in `.tcc` file (auto-included in header)
- Legacy `moveable_types` behavior unchanged
**Compiler Unit Tests** (`compiler/cpp/tests/cpp/`):
- New `test_forward_setter.thrift` fixture
- Dedicated `t_cpp_generator_forward_setter_tests.cc` (91 assertions, 9 test cases)
- Verify `.tcc` generation and template implementations
**Integration Tests** (`test/cpp/src/`):
- `ForwardSetterTest.cpp` - validates lvalue/rvalue/temporary/literal setters with move semantics
- `PrivateOptionalTest.cpp` - SFINAE + static_assert verify optional fields are private
- `EnumClassTest.cpp` - type_traits + static_assert verify true enum class semantics
**CMakeLists.txt** (`test/cpp/`):
- Separate gen-cpp-{forward,private,enumclass} directories
**Makefile.am** (`test/cpp/`):
- Library targets for each option variant
- Proper `BUILT_SOURCES` dependencies
- Include path ordering: option-specific directory before standard `gen-cpp`
```cpp
// Generated with --gen cpp:moveable_types=forward_setter
struct TestStruct {
int32_t primitive_field;
std::string complex_field;
void __set_primitive_field(const int32_t val); // Traditional
template <typename T_>
void __set_complex_field(T_&& val); // Perfect forwarding
};
// In .tcc file:
template <typename T_>
void TestStruct::__set_complex_field(T_&& val) {
this->complex_field = ::std::forward<T_>(val);
__isset.complex_field = true;
}
```
- [ ] Did you create an [Apache Jira](https://issues.apache.org/jira/projects/THRIFT/issues/) ticket? ([Request account here](https://selfserve.apache.org/jira-account.html), not required for trivial changes)
- [ ] If a ticket exists: Does your pull request title follow the pattern "THRIFT-NNNN: describe my issue"?
- [x] Did you squash your changes to a single commit? (not required, but preferred)
- [x] Did you do your best to avoid breaking changes? If one was needed, did you label the Jira ticket with "Breaking-Change"?
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: zsy056 <1074382+zsy056@users.noreply.github.com>
diff --git a/test/cpp/CMakeLists.txt b/test/cpp/CMakeLists.txt
index a6c1fd5..2403c87 100644
--- a/test/cpp/CMakeLists.txt
+++ b/test/cpp/CMakeLists.txt
@@ -95,6 +95,75 @@
target_link_libraries(SpecificNameTest thriftnb)
add_test(NAME SpecificNameTest COMMAND SpecificNameTest)
+# ForwardSetterTest - tests the forward_setter option
+set(forwardsettertestgencpp_SOURCES
+ gen-cpp-forward/gen-cpp/ThriftTest_types.cpp
+ gen-cpp-forward/gen-cpp/ThriftTest_constants.cpp
+ src/ThriftTest_extras.cpp
+)
+add_library(forwardsettertestgencpp STATIC ${forwardsettertestgencpp_SOURCES})
+target_include_directories(forwardsettertestgencpp BEFORE PRIVATE
+ "${CMAKE_CURRENT_BINARY_DIR}/gen-cpp-forward"
+ "${CMAKE_CURRENT_BINARY_DIR}"
+ "${PROJECT_SOURCE_DIR}/lib/cpp/src"
+)
+target_link_libraries(forwardsettertestgencpp thrift)
+
+add_executable(ForwardSetterTest src/ForwardSetterTest.cpp)
+target_include_directories(ForwardSetterTest BEFORE PRIVATE
+ "${CMAKE_CURRENT_BINARY_DIR}/gen-cpp-forward/gen-cpp"
+ "${CMAKE_CURRENT_BINARY_DIR}/gen-cpp-forward"
+)
+target_link_libraries(ForwardSetterTest forwardsettertestgencpp ${Boost_LIBRARIES})
+target_link_libraries(ForwardSetterTest thrift)
+add_test(NAME ForwardSetterTest COMMAND ForwardSetterTest)
+
+# PrivateOptionalTest - tests the private_optional option
+set(privateoptonaltestgencpp_SOURCES
+ gen-cpp-private/gen-cpp/ThriftTest_types.cpp
+ gen-cpp-private/gen-cpp/ThriftTest_constants.cpp
+ src/ThriftTest_extras.cpp
+)
+add_library(privateoptonaltestgencpp STATIC ${privateoptonaltestgencpp_SOURCES})
+target_include_directories(privateoptonaltestgencpp BEFORE PRIVATE
+ "${CMAKE_CURRENT_BINARY_DIR}/gen-cpp-private"
+ "${CMAKE_CURRENT_BINARY_DIR}"
+ "${PROJECT_SOURCE_DIR}/lib/cpp/src"
+)
+target_link_libraries(privateoptonaltestgencpp thrift)
+
+add_executable(PrivateOptionalTest src/PrivateOptionalTest.cpp)
+target_include_directories(PrivateOptionalTest BEFORE PRIVATE
+ "${CMAKE_CURRENT_BINARY_DIR}/gen-cpp-private/gen-cpp"
+ "${CMAKE_CURRENT_BINARY_DIR}/gen-cpp-private"
+)
+target_link_libraries(PrivateOptionalTest privateoptonaltestgencpp ${Boost_LIBRARIES})
+target_link_libraries(PrivateOptionalTest thrift)
+add_test(NAME PrivateOptionalTest COMMAND PrivateOptionalTest)
+
+# EnumClassTest - tests the pure_enums=enum_class option
+set(enumclasstestgencpp_SOURCES
+ gen-cpp-enumclass/gen-cpp/ThriftTest_types.cpp
+ gen-cpp-enumclass/gen-cpp/ThriftTest_constants.cpp
+ src/ThriftTest_extras.cpp
+)
+add_library(enumclasstestgencpp STATIC ${enumclasstestgencpp_SOURCES})
+target_include_directories(enumclasstestgencpp BEFORE PRIVATE
+ "${CMAKE_CURRENT_BINARY_DIR}/gen-cpp-enumclass"
+ "${CMAKE_CURRENT_BINARY_DIR}"
+ "${PROJECT_SOURCE_DIR}/lib/cpp/src"
+)
+target_link_libraries(enumclasstestgencpp thrift)
+
+add_executable(EnumClassTest src/EnumClassTest.cpp)
+target_include_directories(EnumClassTest BEFORE PRIVATE
+ "${CMAKE_CURRENT_BINARY_DIR}/gen-cpp-enumclass/gen-cpp"
+ "${CMAKE_CURRENT_BINARY_DIR}/gen-cpp-enumclass"
+)
+target_link_libraries(EnumClassTest enumclasstestgencpp ${Boost_LIBRARIES})
+target_link_libraries(EnumClassTest thrift)
+add_test(NAME EnumClassTest COMMAND EnumClassTest)
+
#
# Common thrift code generation rules
#
@@ -103,6 +172,27 @@
COMMAND ${THRIFT_COMPILER} --gen cpp:templates,cob_style -r ${PROJECT_SOURCE_DIR}/test/ThriftTest.thrift
)
+# Generate ThriftTest with forward_setter option for ForwardSetterTest
+add_custom_command(OUTPUT gen-cpp-forward/gen-cpp/ThriftTest_types.cpp gen-cpp-forward/gen-cpp/ThriftTest_types.h gen-cpp-forward/gen-cpp/ThriftTest_types.tcc gen-cpp-forward/gen-cpp/ThriftTest_constants.cpp
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/gen-cpp-forward
+ COMMAND ${THRIFT_COMPILER} --gen cpp:moveable_types=forward_setter -o gen-cpp-forward ${PROJECT_SOURCE_DIR}/test/ThriftTest.thrift
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+)
+
+# Generate ThriftTest with private_optional option for PrivateOptionalTest
+add_custom_command(OUTPUT gen-cpp-private/gen-cpp/ThriftTest_types.cpp gen-cpp-private/gen-cpp/ThriftTest_types.h gen-cpp-private/gen-cpp/ThriftTest_constants.cpp
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/gen-cpp-private
+ COMMAND ${THRIFT_COMPILER} --gen cpp:private_optional -o gen-cpp-private ${PROJECT_SOURCE_DIR}/test/ThriftTest.thrift
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+)
+
+# Generate ThriftTest with pure_enums=enum_class option for EnumClassTest
+add_custom_command(OUTPUT gen-cpp-enumclass/gen-cpp/ThriftTest_types.cpp gen-cpp-enumclass/gen-cpp/ThriftTest_types.h gen-cpp-enumclass/gen-cpp/ThriftTest_constants.cpp
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/gen-cpp-enumclass
+ COMMAND ${THRIFT_COMPILER} --gen cpp:pure_enums=enum_class -o gen-cpp-enumclass ${PROJECT_SOURCE_DIR}/test/ThriftTest.thrift
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+)
+
add_custom_command(OUTPUT gen-cpp/Service.cpp
COMMAND ${THRIFT_COMPILER} --gen cpp ${PROJECT_SOURCE_DIR}/test/StressTest.thrift
)