From a051768f012da2eb8a5eae75e7939b9621f41719 Mon Sep 17 00:00:00 2001 From: Centimo Date: Sat, 9 May 2026 21:08:53 +0300 Subject: [PATCH 1/8] Add unified rfl::Settings::with<>() API for format-specific settings Introduce RFL_SETTINGS_OPS(Derived) macro that injects two with<>() overloads into a settings struct: with<&T::field>(value) (by pointer-to-member) and with<"field">(value) (by name). Both return a copy with the chosen const field replaced. Migrate csv::Settings and parquet::Settings off ad-hoc with_*() methods and onto the unified API. The old per-field with_delimiter() / with_compression() / etc. are removed; tests are updated. Implementation details: - rfl::internal::field_index_v recovers the index of a data member by comparing &(fake_object().*FieldPtr) to the addresses of the structured-binding fields, all in a consteval context. - rfl::internal::field_index_by_name_v recovers the index by walking field names through the existing __PRETTY_FUNCTION__ parser. - Both indices are then mapped back to a fake-object pointer of the shape the existing get_field_name_str_lit expects (i.e. the form &fake.field, which MSVC parses correctly), so the rest of the machinery is unchanged. - get_fake_object.hpp switches from a `static const wrapper` member to an `extern const fake_object_holder` variable template, matching Boost.PFR. This lets GCC fold &(fake.*FieldPtr) inside a consteval function without requiring the symbol at link time. CRTP via inheritance from rfl::Settings is intentionally avoided: an empty base class confuses num_fields (counted as a slot, but invisible to structured bindings), which would break the fake-object machinery for any settings struct that uses the macro. The macro keeps the struct a flat aggregate. Add tests/cli/test_settings_macro.cpp covering single replace, chained replace by PTM, chained replace by name, mixed PTM+name chains, and string move semantics. --- include/rfl.hpp | 1 + include/rfl/Settings.hpp | 98 +++++++++++++++++++ include/rfl/csv/Settings.hpp | 69 +++---------- include/rfl/internal/field_index_by_name.hpp | 66 +++++++++++++ include/rfl/internal/field_index_from_ptm.hpp | 52 ++++++++++ include/rfl/internal/get_fake_object.hpp | 11 ++- include/rfl/parquet/Settings.hpp | 15 +-- tests/cli/test_settings_macro.cpp | 75 ++++++++++++++ tests/csv/test_save_load.cpp | 2 +- tests/csv/test_settings.cpp | 2 +- tests/parquet/test_gzip.cpp | 2 +- 11 files changed, 320 insertions(+), 73 deletions(-) create mode 100644 include/rfl/Settings.hpp create mode 100644 include/rfl/internal/field_index_by_name.hpp create mode 100644 include/rfl/internal/field_index_from_ptm.hpp create mode 100644 tests/cli/test_settings_macro.cpp diff --git a/include/rfl.hpp b/include/rfl.hpp index a8c6dcff..f0d0be8a 100644 --- a/include/rfl.hpp +++ b/include/rfl.hpp @@ -80,6 +80,7 @@ #include "rfl/patterns.hpp" #include "rfl/remove_fields.hpp" #include "rfl/replace.hpp" +#include "rfl/Settings.hpp" #include "rfl/to_generic.hpp" #include "rfl/to_named_tuple.hpp" #include "rfl/to_view.hpp" diff --git a/include/rfl/Settings.hpp b/include/rfl/Settings.hpp new file mode 100644 index 00000000..c6ba6d45 --- /dev/null +++ b/include/rfl/Settings.hpp @@ -0,0 +1,98 @@ +#ifndef RFL_SETTINGS_HPP_ +#define RFL_SETTINGS_HPP_ + +#include +#include +#include + +#include "Field.hpp" +#include "internal/StringLiteral.hpp" +#include "internal/field_index_by_name.hpp" +#include "internal/field_index_from_ptm.hpp" +#include "internal/get_field_names.hpp" +#include "internal/get_ith_field_from_fake_object.hpp" +#include "replace.hpp" + +namespace rfl::internal { + +template +struct member_ptr_traits : std::false_type {}; + +template +struct member_ptr_traits + : std::bool_constant && + std::is_const_v> {}; + +template +concept const_member_of = member_ptr_traits::value; + +/// Returns a copy of `_obj` with the field designated by `FieldPtr` replaced +/// by `_value`. Used as the body of the with() method generated by +/// RFL_SETTINGS_OPS. Field name is recovered through the fake-object path +/// that the rest of reflect-cpp uses, so MSVC parses it correctly. +template + requires const_member_of +T settings_with_replaced( + const T& _obj, + std::remove_const_t< + std::remove_reference_t().*FieldPtr)>> + _value) { + constexpr std::size_t I = field_index_v; + static_assert( + I != static_cast(-1), + "FieldPtr does not refer to a non-static data member of T. " + "Make sure you pass &T::field, where field is declared inside T."); + constexpr auto name = + get_field_name_str_lit()>(); + return rfl::replace(_obj, rfl::make_field(std::move(_value))); +} + +/// Returns a copy of `_obj` with the field with the given Name replaced by +/// `_value`. Used as the body of the with<"name">() overload generated by +/// RFL_SETTINGS_OPS. +template +T settings_with_replaced_by_name( + const T& _obj, + field_type_at_t> _value) { + constexpr std::size_t I = field_index_by_name_v; + static_assert(I != static_cast(-1), + "No field with the given name exists in T."); + return rfl::replace(_obj, rfl::make_field(std::move(_value))); +} + +} // namespace rfl::internal + +/// Defines the standard with<&T::field>(value) and with<"field">(value) +/// accessors inside a settings struct. The struct must be a flat aggregate +/// (no base classes). Fields should be declared const so the only way to +/// mutate them is via with(), which returns a new copy with the chosen field +/// replaced. Place the macro at the end of the struct body, after all data +/// members. +/// +/// Usage: +/// struct MySettings { +/// const int some_option = 42; +/// RFL_SETTINGS_OPS(MySettings) +/// }; +/// +/// auto a = MySettings{}.with<&MySettings::some_option>(100); +/// auto b = MySettings{}.with<"some_option">(100); +#define RFL_SETTINGS_OPS(Derived) \ + template \ + Derived with(std::remove_const_t().*FieldPtr)>> \ + _value) const { \ + return ::rfl::internal::settings_with_replaced( \ + *this, std::move(_value)); \ + } \ + template <::rfl::internal::StringLiteral Name> \ + Derived with(::rfl::internal::field_type_at_t< \ + Derived, \ + ::rfl::internal::field_index_by_name_v> \ + _value) const { \ + return ::rfl::internal::settings_with_replaced_by_name( \ + *this, std::move(_value)); \ + } + +#endif diff --git a/include/rfl/csv/Settings.hpp b/include/rfl/csv/Settings.hpp index fa015213..137d3bd0 100644 --- a/include/rfl/csv/Settings.hpp +++ b/include/rfl/csv/Settings.hpp @@ -4,8 +4,7 @@ #include #include -#include "../Field.hpp" -#include "../replace.hpp" +#include "../Settings.hpp" namespace rfl::csv { @@ -13,83 +12,41 @@ struct Settings { /// Maximum number of rows processed at a time. /// Data is processed in batches of N rows. This number /// can impact performance. - int32_t batch_size = 1024; + const int32_t batch_size = 1024; /// Field delimiter. - char delimiter = ','; + const char delimiter = ','; /// Whether quoting is used. - bool quoting = true; + const bool quoting = true; /// Quoting character (if quoting is true). Only relevant for reading. - char quote_char = '"'; + const char quote_char = '"'; /// The string to be used for null values. Quotes are not allowed in this /// string. - std::string null_string = "n/a"; + const std::string null_string = "n/a"; /// Whether a quote inside a value is double-quoted. Only relevant for /// reading. - bool double_quote = true; + const bool double_quote = true; /// Whether escaping is used. Only relevant for reading. - bool escaping = false; + const bool escaping = false; /// Escaping character (if escaping is true). Only relevant for reading. - char escape_char = arrow::csv::kDefaultEscapeChar; + const char escape_char = arrow::csv::kDefaultEscapeChar; /// Whether values are allowed to contain CR (0x0d) and LF (0x0a) /// characters. Only relevant for reading. - bool newlines_in_values = false; + const bool newlines_in_values = false; /// Whether empty lines are ignored. /// If false, an empty line represents a single empty value (assuming a /// one-column CSV file). Only relevant for reading. - bool ignore_empty_lines = true; - - Settings with_batch_size(const int32_t _batch_size) const noexcept { - return replace(*this, make_field<"batch_size">(_batch_size)); - } - - Settings with_delimiter(const char _delimiter) const noexcept { - return replace(*this, make_field<"delimiter">(_delimiter)); - } - - Settings with_quoting(const bool _quoting) const noexcept { - return replace(*this, make_field<"quoting">(_quoting)); - } - - Settings with_quote_char(const char _quote_char) const noexcept { - return replace(*this, make_field<"quote_char">(_quote_char)); - } - - Settings with_null_string(const std::string& _null_string) const noexcept { - return replace(*this, make_field<"null_string">(_null_string)); - } - - Settings with_double_quote(const bool _double_quote) const noexcept { - return replace(*this, make_field<"double_quote">(_double_quote)); - } - - Settings with_escaping(const bool _escaping) const noexcept { - return replace(*this, make_field<"escaping">(_escaping)); - } - - Settings with_escape_char(const char _escape_char) const noexcept { - return replace(*this, make_field<"escape_char">(_escape_char)); - } - - Settings with_newlines_in_values( - const bool _newlines_in_values) const noexcept { - return replace(*this, - make_field<"newlines_in_values">(_newlines_in_values)); - } - - Settings with_ignore_empty_lines( - const bool _ignore_empty_lines) const noexcept { - return replace(*this, - make_field<"ignore_empty_lines">(_ignore_empty_lines)); - } + const bool ignore_empty_lines = true; + + RFL_SETTINGS_OPS(Settings) }; } // namespace rfl::csv diff --git a/include/rfl/internal/field_index_by_name.hpp b/include/rfl/internal/field_index_by_name.hpp new file mode 100644 index 00000000..d3e746a7 --- /dev/null +++ b/include/rfl/internal/field_index_by_name.hpp @@ -0,0 +1,66 @@ +#ifndef RFL_INTERNAL_FIELD_INDEX_BY_NAME_HPP_ +#define RFL_INTERNAL_FIELD_INDEX_BY_NAME_HPP_ + +#include +#include +#include + +#include "StringLiteral.hpp" +#include "field_index_from_ptm.hpp" +#include "get_field_names.hpp" +#include "get_ith_field_from_fake_object.hpp" +#include "num_fields.hpp" + +namespace rfl::internal { + +/// Returns the name of the i-th field of T as a string_view, using the same +/// fake-object name-lookup path that to_named_tuple uses. +template +consteval std::string_view field_name_at() { + return get_field_name_str_view()>(); +} + +template +consteval std::size_t field_index_by_name_impl(std::index_sequence) { + std::size_t result = static_cast(-1); + ((field_name_at() == Name.string_view() + ? (result = Is, true) + : false) || + ...); + return result; +} + +/// Finds the index of the field with the given Name in T. Returns size_t(-1) +/// if no field with that name exists; callers must produce a static_assert. +template +inline constexpr std::size_t field_index_by_name_v = + field_index_by_name_impl( + std::make_index_sequence>{}); + +/// Helper: extracts the value type the i-th fake-object pointer points to. +template +struct field_type_at { + using type = std::remove_cv_t(I)>())>>; +}; + +/// Sentinel value type used when field_index_by_name_v fails to find a +/// matching field. The caller's static_assert then fires before any +/// real call site can use this type. +struct unresolved_field_type {}; + +/// When the index is the sentinel size_t(-1), return a placeholder type so +/// that a failing field_index_by_name_v lookup does not trip an out-of-range +/// instantiation before the caller's static_assert can fire. +template +struct field_type_at(-1)> { + using type = unresolved_field_type; +}; + +template +using field_type_at_t = typename field_type_at::type; + +} // namespace rfl::internal + +#endif diff --git a/include/rfl/internal/field_index_from_ptm.hpp b/include/rfl/internal/field_index_from_ptm.hpp new file mode 100644 index 00000000..6d018a67 --- /dev/null +++ b/include/rfl/internal/field_index_from_ptm.hpp @@ -0,0 +1,52 @@ +#ifndef RFL_INTERNAL_FIELD_INDEX_FROM_PTM_HPP_ +#define RFL_INTERNAL_FIELD_INDEX_FROM_PTM_HPP_ + +#include +#include + +#include "get_fake_object.hpp" +#include "get_field_names.hpp" +#include "get_ith_field_from_fake_object.hpp" +#include "num_fields.hpp" + +namespace rfl::internal { + +/// Finds the index of the data member designated by `FieldPtr` in T by +/// comparing the address of (fake_object().*FieldPtr) against the +/// addresses of T's structured-binding fields. All comparisons are between +/// subobject pointers of the same fake object, which is well-defined in a +/// constant expression. Returns size_t(-1) if no match is found, in which +/// case callers must produce a static_assert. +template +consteval std::size_t field_index_from_ptm_impl(std::index_sequence) { + const void* target = + static_cast(&(get_fake_object().*FieldPtr)); + std::size_t result = static_cast(-1); + ((static_cast(get_ith_field_from_fake_object()) == target + ? (result = Is, true) + : false) || + ...); + return result; +} + +template +inline constexpr std::size_t field_index_v = + field_index_from_ptm_impl( + std::make_index_sequence>{}); + +/// Returns the i-th field pointer of the fake object of T in the form +/// `get_field_name_str_lit` expects on the current compiler. On clang the +/// pointer is wrapped in `Wrapper` because clang's parser of __PRETTY_FUNCTION__ +/// extracts the field name from the wrapper's type, not from the value. +template +consteval auto fake_field_ptr_for_name_lookup() { +#if defined(__clang__) + return wrap(get_ith_field_from_fake_object(I)>()); +#else + return get_ith_field_from_fake_object(I)>(); +#endif +} + +} // namespace rfl::internal + +#endif diff --git a/include/rfl/internal/get_fake_object.hpp b/include/rfl/internal/get_fake_object.hpp index 27f7fc5b..134c5c87 100644 --- a/include/rfl/internal/get_fake_object.hpp +++ b/include/rfl/internal/get_fake_object.hpp @@ -22,14 +22,19 @@ namespace internal { #endif template -struct wrapper { +struct fake_object_holder { const T value; - static const wrapper report_if_you_see_a_link_error_with_this_object; }; +// Declared but never defined. Distinct from rfl::internal::Wrapper +// (which is a payload wrapper used in name-lookup NTTPs). +template +extern const fake_object_holder + report_if_you_see_a_link_error_with_this_object; + template consteval const T& get_fake_object() noexcept { - return wrapper::report_if_you_see_a_link_error_with_this_object.value; + return report_if_you_see_a_link_error_with_this_object.value; } #ifdef __clang__ diff --git a/include/rfl/parquet/Settings.hpp b/include/rfl/parquet/Settings.hpp index 2ba65def..78b2cd0e 100644 --- a/include/rfl/parquet/Settings.hpp +++ b/include/rfl/parquet/Settings.hpp @@ -4,8 +4,7 @@ #include #include -#include "../Field.hpp" -#include "../replace.hpp" +#include "../Settings.hpp" namespace rfl::parquet { @@ -13,18 +12,12 @@ using Compression = arrow::Compression::type; struct Settings { /// The size of the chunks of the parquet file. - size_t chunksize = 2000; + const size_t chunksize = 2000; /// The compression algorithm used to compress the parquet file. - Compression compression = Compression::SNAPPY; + const Compression compression = Compression::SNAPPY; - Settings with_chunksize(const size_t _chunksize) const noexcept { - return replace(*this, make_field<"chunksize">(_chunksize)); - } - - Settings with_compression(const Compression _compression) const noexcept { - return replace(*this, make_field<"compression">(_compression)); - } + RFL_SETTINGS_OPS(Settings) }; } // namespace rfl::parquet diff --git a/tests/cli/test_settings_macro.cpp b/tests/cli/test_settings_macro.cpp new file mode 100644 index 00000000..d30d28e2 --- /dev/null +++ b/tests/cli/test_settings_macro.cpp @@ -0,0 +1,75 @@ +#include +#include +#include + +namespace test_settings_macro { + +struct DemoSettings { + const int batch_size = 1024; + const char delimiter = ','; + const std::string null_string = "n/a"; + const bool quoting = true; + + RFL_SETTINGS_OPS(DemoSettings) +}; + +TEST(settings_macro, single_field_replace_returns_new_copy) { + const DemoSettings original{}; + const auto modified = + original.with<&DemoSettings::delimiter>(';'); + + EXPECT_EQ(original.delimiter, ','); + EXPECT_EQ(modified.delimiter, ';'); + EXPECT_EQ(modified.batch_size, original.batch_size); + EXPECT_EQ(modified.null_string, original.null_string); + EXPECT_EQ(modified.quoting, original.quoting); +} + +TEST(settings_macro, chained_with_calls_apply_all_replacements) { + const auto modified = DemoSettings{} + .with<&DemoSettings::batch_size>(2048) + .with<&DemoSettings::delimiter>('|') + .with<&DemoSettings::null_string>(std::string{"NA"}) + .with<&DemoSettings::quoting>(false); + + EXPECT_EQ(modified.batch_size, 2048); + EXPECT_EQ(modified.delimiter, '|'); + EXPECT_EQ(modified.null_string, "NA"); + EXPECT_FALSE(modified.quoting); +} + +TEST(settings_macro, replace_string_field_moves_value) { + // The with<> parameter is passed by value, so passing an rvalue lets the + // implementation move into make_field. Result must equal the source. + std::string moved_in = "moved-in-value"; + const auto modified = + DemoSettings{}.with<&DemoSettings::null_string>(std::move(moved_in)); + EXPECT_EQ(modified.null_string, "moved-in-value"); +} + +TEST(settings_macro, by_name_replace_works_for_each_field) { + const auto modified = DemoSettings{}.with<"delimiter">(';'); + EXPECT_EQ(modified.delimiter, ';'); + EXPECT_EQ(modified.batch_size, 1024); +} + +TEST(settings_macro, by_name_chained_calls_apply_all_replacements) { + const auto modified = DemoSettings{} + .with<"batch_size">(4096) + .with<"delimiter">('\t') + .with<"null_string">(std::string{"NULL"}) + .with<"quoting">(false); + EXPECT_EQ(modified.batch_size, 4096); + EXPECT_EQ(modified.delimiter, '\t'); + EXPECT_EQ(modified.null_string, "NULL"); + EXPECT_FALSE(modified.quoting); +} + +TEST(settings_macro, by_name_and_by_ptm_are_interchangeable) { + const auto by_ptm = DemoSettings{}.with<&DemoSettings::delimiter>(';'); + const auto by_name = DemoSettings{}.with<"delimiter">(';'); + EXPECT_EQ(by_ptm.delimiter, by_name.delimiter); + EXPECT_EQ(by_ptm.batch_size, by_name.batch_size); +} + +} // namespace test_settings_macro diff --git a/tests/csv/test_save_load.cpp b/tests/csv/test_save_load.cpp index 14ef4ff2..43abf314 100644 --- a/tests/csv/test_save_load.cpp +++ b/tests/csv/test_save_load.cpp @@ -39,7 +39,7 @@ TEST(csv, test_save_load) { .age = 45, .email = "homer@simpson.com"}}); - const auto settings = rfl::csv::Settings{}.with_delimiter(';'); + const auto settings = rfl::csv::Settings{}.with<&rfl::csv::Settings::delimiter>(';'); rfl::csv::save("people.csv", people1, settings); diff --git a/tests/csv/test_settings.cpp b/tests/csv/test_settings.cpp index ca3ddbb1..03ad0298 100644 --- a/tests/csv/test_settings.cpp +++ b/tests/csv/test_settings.cpp @@ -36,7 +36,7 @@ TEST(csv, test_settings) { .age = 45, .email = "homer@simpson.com"}}); - const auto settings = rfl::csv::Settings{}.with_delimiter(';'); + const auto settings = rfl::csv::Settings{}.with<&rfl::csv::Settings::delimiter>(';'); write_and_read(people, settings); } diff --git a/tests/parquet/test_gzip.cpp b/tests/parquet/test_gzip.cpp index 62f45f7f..cf600d89 100644 --- a/tests/parquet/test_gzip.cpp +++ b/tests/parquet/test_gzip.cpp @@ -36,7 +36,7 @@ TEST(parquet, test_gzip) { .age = 45, .email = "homer@simpson.com"}}); - const auto settings = rfl::parquet::Settings{}.with_compression( + const auto settings = rfl::parquet::Settings{}.with<&rfl::parquet::Settings::compression>( rfl::parquet::Compression::GZIP); write_and_read(people, settings); From 5c17f359f1bffc08797509df22298291cf292779 Mon Sep 17 00:00:00 2001 From: Centimo Date: Sat, 9 May 2026 21:30:17 +0300 Subject: [PATCH 2/8] docs: update csv and parquet Settings examples for the new with<>() API Replace the removed with_delimiter() / with_compression() / etc. snippets with the unified with<&T::field>(value) form, and show the equivalent with<"field">(value) variant. --- docs/supported_formats/csv.md | 37 ++++++++++++++++++++----------- docs/supported_formats/parquet.md | 29 ++++++++++++++++-------- 2 files changed, 44 insertions(+), 22 deletions(-) diff --git a/docs/supported_formats/csv.md b/docs/supported_formats/csv.md index 0065ea67..eeb2fb6e 100644 --- a/docs/supported_formats/csv.md +++ b/docs/supported_formats/csv.md @@ -40,24 +40,35 @@ const rfl::Result> result = rfl::csv::read()` accessor that takes either a pointer-to-member or +the field name as a template argument and returns a new copy of `Settings` +with the chosen field replaced: ```cpp const auto settings = rfl::csv::Settings{} - .with_delimiter(';') - .with_quoting(true) - .with_quote_char('"') - .with_null_string("n/a") - .with_double_quote(true) - .with_escaping(false) - .with_escape_char('\\') - .with_newlines_in_values(false) - .with_ignore_empty_lines(true) - .with_batch_size(1024); + .with<&rfl::csv::Settings::delimiter>(';') + .with<&rfl::csv::Settings::quoting>(true) + .with<&rfl::csv::Settings::quote_char>('"') + .with<&rfl::csv::Settings::null_string>("n/a") + .with<&rfl::csv::Settings::double_quote>(true) + .with<&rfl::csv::Settings::escaping>(false) + .with<&rfl::csv::Settings::escape_char>('\\') + .with<&rfl::csv::Settings::newlines_in_values>(false) + .with<&rfl::csv::Settings::ignore_empty_lines>(true) + .with<&rfl::csv::Settings::batch_size>(1024); const std::string csv_text = rfl::csv::write(people, settings); ``` +The same call is also available with a string literal naming the field: + +```cpp +const auto settings = rfl::csv::Settings{} + .with<"delimiter">(';') + .with<"quoting">(true); +``` + Key options: - `batch_size` - Maximum number of rows processed per batch (performance tuning) - `delimiter` - Field delimiter character @@ -84,7 +95,7 @@ rfl::csv::save("/path/to/file.csv", people); With custom settings: ```cpp -const auto settings = rfl::csv::Settings{}.with_delimiter(';'); +const auto settings = rfl::csv::Settings{}.with<"delimiter">(';'); rfl::csv::save("/path/to/file.csv", people, settings); ``` @@ -102,7 +113,7 @@ rfl::csv::write(people, my_ostream); With custom settings: ```cpp -const auto settings = rfl::csv::Settings{}.with_delimiter(';'); +const auto settings = rfl::csv::Settings{}.with<"delimiter">(';'); rfl::csv::write(people, my_ostream, settings); ``` diff --git a/docs/supported_formats/parquet.md b/docs/supported_formats/parquet.md index 0cba3e1b..b4723061 100644 --- a/docs/supported_formats/parquet.md +++ b/docs/supported_formats/parquet.md @@ -39,16 +39,27 @@ const rfl::Result> result = rfl::parquet::read()` accessor that takes either a pointer-to-member or the +field name as a template argument and returns a new copy of `Settings` with +the chosen field replaced: ```cpp const auto settings = rfl::parquet::Settings{} - .with_compression(rfl::parquet::Compression::GZIP) - .with_chunksize(1000); + .with<&rfl::parquet::Settings::compression>(rfl::parquet::Compression::GZIP) + .with<&rfl::parquet::Settings::chunksize>(1000); const std::vector bytes = rfl::parquet::write(people, settings); ``` +The same call is also available with a string literal naming the field: + +```cpp +const auto settings = rfl::parquet::Settings{} + .with<"compression">(rfl::parquet::Compression::GZIP); +``` + Available compression options include: - `UNCOMPRESSED` - No compression, fastest read/write but largest file size @@ -65,16 +76,16 @@ Available compression options include: ```cpp // Examples of different compression settings const auto snappy_settings = rfl::parquet::Settings{} - .with_compression(rfl::parquet::Compression::SNAPPY); + .with<&rfl::parquet::Settings::compression>(rfl::parquet::Compression::SNAPPY); const auto gzip_settings = rfl::parquet::Settings{} - .with_compression(rfl::parquet::Compression::GZIP); + .with<&rfl::parquet::Settings::compression>(rfl::parquet::Compression::GZIP); const auto zstd_settings = rfl::parquet::Settings{} - .with_compression(rfl::parquet::Compression::ZSTD); + .with<&rfl::parquet::Settings::compression>(rfl::parquet::Compression::ZSTD); const auto uncompressed_settings = rfl::parquet::Settings{} - .with_compression(rfl::parquet::Compression::UNCOMPRESSED); + .with<&rfl::parquet::Settings::compression>(rfl::parquet::Compression::UNCOMPRESSED); ``` ## Loading and saving @@ -92,7 +103,7 @@ With custom settings: ```cpp const auto settings = rfl::parquet::Settings{} - .with_compression(rfl::parquet::Compression::GZIP); + .with<&rfl::parquet::Settings::compression>(rfl::parquet::Compression::GZIP); rfl::parquet::save("/path/to/file.parquet", people, settings); ``` @@ -111,7 +122,7 @@ With custom settings: ```cpp const auto settings = rfl::parquet::Settings{} - .with_compression(rfl::parquet::Compression::GZIP); + .with<&rfl::parquet::Settings::compression>(rfl::parquet::Compression::GZIP); rfl::parquet::write(people, my_ostream, settings); ``` From 98656e173622e6ff72e329bcb87bc9824bb10093 Mon Sep 17 00:00:00 2001 From: Centimo Date: Sat, 9 May 2026 23:07:04 +0300 Subject: [PATCH 3/8] Fix MSVC C2131 in field_index_from_ptm by using a local object MSVC refused to constant-fold &(get_fake_object().*FieldPtr) inside a consteval context (the extern-const fake-object pattern is well-defined on GCC/Clang but not on MSVC). The PTM-to-index lookup was the only call site that did this PTM-deref through the fake object. Reshape field_extractor around a single base function that takes the source object: get_ptr(const T& obj). field_index_from_ptm_impl now constructs a local T{} and compares &(obj.*FieldPtr) against get_ith_field_ptr(obj), so no extern-const symbol is dereferenced. get_ith_field_from_fake_object becomes a thin wrapper over get_ith_field_ptr passing get_fake_object(), preserving its public signature and behaviour for name-lookup callers. Also revert get_fake_object.hpp to its pre-PR static-member form (the extern-const variant was added for a fake-object PTM-deref path that no longer exists), and enforce that Settings fields named via with<"name">() are const. --- include/rfl/Settings.hpp | 4 ++++ include/rfl/internal/field_index_from_ptm.hpp | 20 ++++++++++--------- include/rfl/internal/get_fake_object.hpp | 11 +++------- .../get_ith_field_from_fake_object.hpp | 17 ++++++++++------ 4 files changed, 29 insertions(+), 23 deletions(-) diff --git a/include/rfl/Settings.hpp b/include/rfl/Settings.hpp index c6ba6d45..55c5dbac 100644 --- a/include/rfl/Settings.hpp +++ b/include/rfl/Settings.hpp @@ -58,6 +58,10 @@ T settings_with_replaced_by_name( constexpr std::size_t I = field_index_by_name_v; static_assert(I != static_cast(-1), "No field with the given name exists in T."); + using FieldTypeCv = std::remove_pointer_t(I)>())>; + static_assert(std::is_const_v, + "Fields in Settings structs must be const."); return rfl::replace(_obj, rfl::make_field(std::move(_value))); } diff --git a/include/rfl/internal/field_index_from_ptm.hpp b/include/rfl/internal/field_index_from_ptm.hpp index 6d018a67..80296f34 100644 --- a/include/rfl/internal/field_index_from_ptm.hpp +++ b/include/rfl/internal/field_index_from_ptm.hpp @@ -4,7 +4,6 @@ #include #include -#include "get_fake_object.hpp" #include "get_field_names.hpp" #include "get_ith_field_from_fake_object.hpp" #include "num_fields.hpp" @@ -12,17 +11,20 @@ namespace rfl::internal { /// Finds the index of the data member designated by `FieldPtr` in T by -/// comparing the address of (fake_object().*FieldPtr) against the -/// addresses of T's structured-binding fields. All comparisons are between -/// subobject pointers of the same fake object, which is well-defined in a -/// constant expression. Returns size_t(-1) if no match is found, in which -/// case callers must produce a static_assert. +/// comparing the address of (obj.*FieldPtr) against the addresses of T's +/// structured-binding fields, where obj is a locally default-constructed T. +/// All comparisons are between subobject pointers of the same local object, +/// which is well-defined in a constant expression. T must be +/// default-constructible in a constant expression. +/// Returns size_t(-1) if no match is found, in which case callers must +/// produce a static_assert. template consteval std::size_t field_index_from_ptm_impl(std::index_sequence) { - const void* target = - static_cast(&(get_fake_object().*FieldPtr)); + T obj{}; + const void* target = static_cast(&(obj.*FieldPtr)); std::size_t result = static_cast(-1); - ((static_cast(get_ith_field_from_fake_object()) == target + ((static_cast(get_ith_field_ptr(Is)>(obj)) == + target ? (result = Is, true) : false) || ...); diff --git a/include/rfl/internal/get_fake_object.hpp b/include/rfl/internal/get_fake_object.hpp index 134c5c87..27f7fc5b 100644 --- a/include/rfl/internal/get_fake_object.hpp +++ b/include/rfl/internal/get_fake_object.hpp @@ -22,19 +22,14 @@ namespace internal { #endif template -struct fake_object_holder { +struct wrapper { const T value; + static const wrapper report_if_you_see_a_link_error_with_this_object; }; -// Declared but never defined. Distinct from rfl::internal::Wrapper -// (which is a payload wrapper used in name-lookup NTTPs). -template -extern const fake_object_holder - report_if_you_see_a_link_error_with_this_object; - template consteval const T& get_fake_object() noexcept { - return report_if_you_see_a_link_error_with_this_object.value; + return wrapper::report_if_you_see_a_link_error_with_this_object.value; } #ifdef __clang__ diff --git a/include/rfl/internal/get_ith_field_from_fake_object.hpp b/include/rfl/internal/get_ith_field_from_fake_object.hpp index 6707ae11..7f5c57a9 100644 --- a/include/rfl/internal/get_ith_field_from_fake_object.hpp +++ b/include/rfl/internal/get_ith_field_from_fake_object.hpp @@ -12,9 +12,9 @@ namespace rfl::internal { template -struct fake_object_helper { +struct field_extractor { template - static consteval auto get_field() { + static consteval auto get_ptr(const std::remove_cvref_t&) { static_assert( rfl::always_false_v, "\n\nThis error occurs for one of two reasons:\n\n" @@ -33,10 +33,10 @@ struct fake_object_helper { #define RFL_INTERNAL_FAKE_OBJECT_IF_YOU_SEE_AN_ERROR_REFER_TO_DOCUMENTATION_ON_C_ARRAYS( \ n, ...) \ template \ - struct fake_object_helper { \ + struct field_extractor { \ template \ - static consteval auto get_field() { \ - const auto& [__VA_ARGS__] = get_fake_object>(); \ + static consteval auto get_ptr(const std::remove_cvref_t& _obj) { \ + const auto& [__VA_ARGS__] = _obj; \ const auto get_ptrs = [](const auto&... _refs) { \ return nth_element<_i>(&_refs...); \ }; \ @@ -2815,9 +2815,14 @@ RFL_INTERNAL_FAKE_OBJECT_IF_YOU_SEE_AN_ERROR_REFER_TO_DOCUMENTATION_ON_C_ARRAYS( #undef RFL_INTERNAL_FAKE_OBJECT_IF_YOU_SEE_AN_ERROR_REFER_TO_DOCUMENTATION_ON_C_ARRAYS +template +consteval auto get_ith_field_ptr(const std::remove_cvref_t& _obj) { + return field_extractor>::template get_ptr<_i>(_obj); +} + template consteval auto get_ith_field_from_fake_object() { - return fake_object_helper>::template get_field<_i>(); + return get_ith_field_ptr(get_fake_object>()); } } // namespace rfl::internal From 85a08ef52f3345fe49256be29e1691f9ba752561 Mon Sep 17 00:00:00 2001 From: Centimo Date: Sun, 10 May 2026 01:15:08 +0300 Subject: [PATCH 4/8] Make ith_field_is_const detect non-const Settings fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous static_assert in settings_with_replaced_by_name extracted the field type via get_ith_field_from_fake_object, which calls get_ptr with a const T& source. Structured-binding `const auto& [...]` over a const object always yields const-qualified references, so the deduced field type was always `const FieldT` regardless of how the field was declared. The assert was vacuously true and never fired. Reshape get_ptr around `auto& [...]` over a cv-templated source object, so field references inherit the source's cv. Calls through get_fake_object() (const T&) keep producing const FieldT* — same behaviour for name-lookup and PTM-index callers. A new helper ith_field_is_const() builds a non-const local T and inspects the deduced field type, correctly distinguishing const from non-const fields. settings_with_replaced_by_name now uses it. Verified: a settings struct with a non-const field now triggers `Fields in Settings structs must be const.` at the with<"name">() call site. --- include/rfl/Settings.hpp | 4 +--- .../get_ith_field_from_fake_object.hpp | 23 ++++++++++++------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/include/rfl/Settings.hpp b/include/rfl/Settings.hpp index 55c5dbac..36d69add 100644 --- a/include/rfl/Settings.hpp +++ b/include/rfl/Settings.hpp @@ -58,9 +58,7 @@ T settings_with_replaced_by_name( constexpr std::size_t I = field_index_by_name_v; static_assert(I != static_cast(-1), "No field with the given name exists in T."); - using FieldTypeCv = std::remove_pointer_t(I)>())>; - static_assert(std::is_const_v, + static_assert(ith_field_is_const(I)>(), "Fields in Settings structs must be const."); return rfl::replace(_obj, rfl::make_field(std::move(_value))); } diff --git a/include/rfl/internal/get_ith_field_from_fake_object.hpp b/include/rfl/internal/get_ith_field_from_fake_object.hpp index 7f5c57a9..8d9dc06b 100644 --- a/include/rfl/internal/get_ith_field_from_fake_object.hpp +++ b/include/rfl/internal/get_ith_field_from_fake_object.hpp @@ -13,8 +13,8 @@ namespace rfl::internal { template struct field_extractor { - template - static consteval auto get_ptr(const std::remove_cvref_t&) { + template + static consteval auto get_ptr(U&) { static_assert( rfl::always_false_v, "\n\nThis error occurs for one of two reasons:\n\n" @@ -34,10 +34,10 @@ struct field_extractor { n, ...) \ template \ struct field_extractor { \ - template \ - static consteval auto get_ptr(const std::remove_cvref_t& _obj) { \ - const auto& [__VA_ARGS__] = _obj; \ - const auto get_ptrs = [](const auto&... _refs) { \ + template \ + static consteval auto get_ptr(U& _obj) { \ + auto& [__VA_ARGS__] = _obj; \ + const auto get_ptrs = [](auto&... _refs) { \ return nth_element<_i>(&_refs...); \ }; \ return get_ptrs(__VA_ARGS__); \ @@ -2815,8 +2815,8 @@ RFL_INTERNAL_FAKE_OBJECT_IF_YOU_SEE_AN_ERROR_REFER_TO_DOCUMENTATION_ON_C_ARRAYS( #undef RFL_INTERNAL_FAKE_OBJECT_IF_YOU_SEE_AN_ERROR_REFER_TO_DOCUMENTATION_ON_C_ARRAYS -template -consteval auto get_ith_field_ptr(const std::remove_cvref_t& _obj) { +template +consteval auto get_ith_field_ptr(U& _obj) { return field_extractor>::template get_ptr<_i>(_obj); } @@ -2825,6 +2825,13 @@ consteval auto get_ith_field_from_fake_object() { return get_ith_field_ptr(get_fake_object>()); } +template +consteval bool ith_field_is_const() { + std::remove_cvref_t _obj{}; + using FieldRef = decltype(*get_ith_field_ptr(_obj)); + return std::is_const_v>; +} + } // namespace rfl::internal #endif From 0eb58cbadf6aad70e4544f88250e704d24a4e40e Mon Sep 17 00:00:00 2001 From: Centimo Date: Sun, 10 May 2026 02:04:59 +0300 Subject: [PATCH 5/8] Surface RFL_SETTINGS_OPS constraints at the call site Two diagnostics moved from deep inside the library to the with<>() call: - Propagate const_member_of into the macro's PTM overload. A call like .with<&T::non_const_field>(value) now fails overload resolution at the call site with a clear "no matching function" diagnostic citing the unsatisfied requires-clause, instead of a deep concept failure inside settings_with_replaced. - Add a static_assert in field_index_from_ptm_impl that T is default-constructible. The local T{} construction in consteval already required this; the assert produces an actionable message instead of a generic constant-expression failure. --- include/rfl/Settings.hpp | 1 + include/rfl/internal/field_index_from_ptm.hpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/include/rfl/Settings.hpp b/include/rfl/Settings.hpp index 36d69add..72353de8 100644 --- a/include/rfl/Settings.hpp +++ b/include/rfl/Settings.hpp @@ -82,6 +82,7 @@ T settings_with_replaced_by_name( /// auto b = MySettings{}.with<"some_option">(100); #define RFL_SETTINGS_OPS(Derived) \ template \ + requires ::rfl::internal::const_member_of \ Derived with(std::remove_const_t().*FieldPtr)>> \ _value) const { \ diff --git a/include/rfl/internal/field_index_from_ptm.hpp b/include/rfl/internal/field_index_from_ptm.hpp index 80296f34..46c0ee30 100644 --- a/include/rfl/internal/field_index_from_ptm.hpp +++ b/include/rfl/internal/field_index_from_ptm.hpp @@ -2,6 +2,7 @@ #define RFL_INTERNAL_FIELD_INDEX_FROM_PTM_HPP_ #include +#include #include #include "get_field_names.hpp" @@ -20,6 +21,9 @@ namespace rfl::internal { /// produce a static_assert. template consteval std::size_t field_index_from_ptm_impl(std::index_sequence) { + static_assert(std::is_default_constructible_v, + "Settings structs used with rfl::Settings::with<&T::field>() " + "must be default-constructible."); T obj{}; const void* target = static_cast(&(obj.*FieldPtr)); std::size_t result = static_cast(-1); From 53b3b70fbf9727bde7ef0a483d5bcf8c71d02602 Mon Sep 17 00:00:00 2001 From: Centimo Date: Sun, 10 May 2026 02:13:49 +0300 Subject: [PATCH 6/8] Rename field_type_at to field_value_type_at MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The alias strips cv from the deduced field type, producing a value type suitable as a function parameter or local variable. The old name read as "the field's type" — misleading, since callers cannot use it to inspect the original field's const-ness. Rename to field_value_type_at(_t) so the name matches what the alias actually yields. No behavioural change. Two call sites in Settings.hpp updated; nothing else used the alias. --- include/rfl/Settings.hpp | 4 ++-- include/rfl/internal/field_index_by_name.hpp | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/include/rfl/Settings.hpp b/include/rfl/Settings.hpp index 72353de8..e5727a06 100644 --- a/include/rfl/Settings.hpp +++ b/include/rfl/Settings.hpp @@ -54,7 +54,7 @@ T settings_with_replaced( template T settings_with_replaced_by_name( const T& _obj, - field_type_at_t> _value) { + field_value_type_at_t> _value) { constexpr std::size_t I = field_index_by_name_v; static_assert(I != static_cast(-1), "No field with the given name exists in T."); @@ -90,7 +90,7 @@ T settings_with_replaced_by_name( *this, std::move(_value)); \ } \ template <::rfl::internal::StringLiteral Name> \ - Derived with(::rfl::internal::field_type_at_t< \ + Derived with(::rfl::internal::field_value_type_at_t< \ Derived, \ ::rfl::internal::field_index_by_name_v> \ _value) const { \ diff --git a/include/rfl/internal/field_index_by_name.hpp b/include/rfl/internal/field_index_by_name.hpp index d3e746a7..52609b51 100644 --- a/include/rfl/internal/field_index_by_name.hpp +++ b/include/rfl/internal/field_index_by_name.hpp @@ -38,9 +38,12 @@ inline constexpr std::size_t field_index_by_name_v = field_index_by_name_impl( std::make_index_sequence>{}); -/// Helper: extracts the value type the i-th fake-object pointer points to. +/// Helper: extracts the cv-stripped value type of the i-th field of T, +/// suitable as a function parameter or local variable type. Use this when +/// you need to declare an object of the field's type; do not use it to +/// inspect const-ness of the original field declaration (cv is removed). template -struct field_type_at { +struct field_value_type_at { using type = std::remove_cv_t(I)>())>>; }; @@ -54,12 +57,12 @@ struct unresolved_field_type {}; /// that a failing field_index_by_name_v lookup does not trip an out-of-range /// instantiation before the caller's static_assert can fire. template -struct field_type_at(-1)> { +struct field_value_type_at(-1)> { using type = unresolved_field_type; }; template -using field_type_at_t = typename field_type_at::type; +using field_value_type_at_t = typename field_value_type_at::type; } // namespace rfl::internal From cebcba2e73ed6488fbb4bb81bb321f8450e6f881 Mon Sep 17 00:00:00 2001 From: Centimo Date: Sun, 10 May 2026 18:19:11 +0300 Subject: [PATCH 7/8] Use local object only on MSVC to avoid GCC 11 literal-type errors The local-T{} path in field_index_from_ptm_impl requires every field of T to be a constexpr literal type. libstdc++ in GCC 11 does not give std::string a constexpr default constructor, so any settings struct with a std::string member fails to instantiate with "temporary of non-literal type 'const string' in a constant expression". The local object was only needed because MSVC refuses to constant-fold &(extern_fake_object.*FieldPtr). Restore the fake-object path on GCC and Clang (works for any field type), keep the local-object path under #ifdef _MSC_VER. ith_field_is_const has the same constraint, so the const-on-name-path check is also MSVC-only; on GCC/Clang the assignment into a const member through rfl::replace fails, providing a (less focussed) diagnostic. --- include/rfl/Settings.hpp | 2 + include/rfl/internal/field_index_from_ptm.hpp | 39 +++++++++++++++---- .../get_ith_field_from_fake_object.hpp | 13 +++++++ 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/include/rfl/Settings.hpp b/include/rfl/Settings.hpp index e5727a06..a1cf2c08 100644 --- a/include/rfl/Settings.hpp +++ b/include/rfl/Settings.hpp @@ -58,8 +58,10 @@ T settings_with_replaced_by_name( constexpr std::size_t I = field_index_by_name_v; static_assert(I != static_cast(-1), "No field with the given name exists in T."); +#ifdef _MSC_VER static_assert(ith_field_is_const(I)>(), "Fields in Settings structs must be const."); +#endif return rfl::replace(_obj, rfl::make_field(std::move(_value))); } diff --git a/include/rfl/internal/field_index_from_ptm.hpp b/include/rfl/internal/field_index_from_ptm.hpp index 46c0ee30..22a7366a 100644 --- a/include/rfl/internal/field_index_from_ptm.hpp +++ b/include/rfl/internal/field_index_from_ptm.hpp @@ -2,9 +2,13 @@ #define RFL_INTERNAL_FIELD_INDEX_FROM_PTM_HPP_ #include -#include #include +#ifdef _MSC_VER +#include +#endif + +#include "get_fake_object.hpp" #include "get_field_names.hpp" #include "get_ith_field_from_fake_object.hpp" #include "num_fields.hpp" @@ -13,17 +17,28 @@ namespace rfl::internal { /// Finds the index of the data member designated by `FieldPtr` in T by /// comparing the address of (obj.*FieldPtr) against the addresses of T's -/// structured-binding fields, where obj is a locally default-constructed T. -/// All comparisons are between subobject pointers of the same local object, -/// which is well-defined in a constant expression. T must be -/// default-constructible in a constant expression. +/// structured-binding fields. All comparisons are between subobject pointers +/// of the same object, which is well-defined in a constant expression. +/// +/// On GCC/Clang we use the extern-const fake object — fields can be of any +/// type (including non-literal `std::string` on older standard libraries), +/// since the object is never actually instantiated, only address-folded +/// inside the `consteval` evaluation. +/// +/// MSVC refuses to constant-fold &(fake_object.*FieldPtr) on an extern-const +/// symbol, so on MSVC we construct a local default-initialised T instead. +/// This requires T (and the field type) to be a constexpr literal type, but +/// settings structs in practice satisfy this when targeting MSVC builds. +/// /// Returns size_t(-1) if no match is found, in which case callers must /// produce a static_assert. template consteval std::size_t field_index_from_ptm_impl(std::index_sequence) { +#ifdef _MSC_VER static_assert(std::is_default_constructible_v, - "Settings structs used with rfl::Settings::with<&T::field>() " - "must be default-constructible."); + "On MSVC, Settings structs used with " + "rfl::Settings::with<&T::field>() must be " + "default-constructible (constexpr literal type)."); T obj{}; const void* target = static_cast(&(obj.*FieldPtr)); std::size_t result = static_cast(-1); @@ -33,6 +48,16 @@ consteval std::size_t field_index_from_ptm_impl(std::index_sequence) { : false) || ...); return result; +#else + const void* target = + static_cast(&(get_fake_object().*FieldPtr)); + std::size_t result = static_cast(-1); + ((static_cast(get_ith_field_from_fake_object()) == target + ? (result = Is, true) + : false) || + ...); + return result; +#endif } template diff --git a/include/rfl/internal/get_ith_field_from_fake_object.hpp b/include/rfl/internal/get_ith_field_from_fake_object.hpp index 8d9dc06b..2820b6a6 100644 --- a/include/rfl/internal/get_ith_field_from_fake_object.hpp +++ b/include/rfl/internal/get_ith_field_from_fake_object.hpp @@ -2825,12 +2825,25 @@ consteval auto get_ith_field_from_fake_object() { return get_ith_field_ptr(get_fake_object>()); } +/// Returns whether the i-th field of T is declared const. Implemented by +/// inspecting the type that get_ith_field_ptr deduces from a non-const T, +/// where structured-binding references inherit the field's cv-qualification. +/// +/// Only available on MSVC: the implementation requires constructing a local +/// T{} inside `consteval`, which forces T (and every field) to be a +/// constexpr literal type. Settings structs in reflect-cpp routinely contain +/// `std::string`, which is not a literal type on older standard libraries +/// (e.g. libstdc++ shipped with GCC 11), so on GCC/Clang this check is +/// skipped — the name-based with<"name">() path then falls through to +/// `rfl::replace`, which will fail to assign into a const member. +#ifdef _MSC_VER template consteval bool ith_field_is_const() { std::remove_cvref_t _obj{}; using FieldRef = decltype(*get_ith_field_ptr(_obj)); return std::is_const_v>; } +#endif } // namespace rfl::internal From bef1f8c3ca7d03f2a3eadd228aab9bb1a9a70fe6 Mon Sep 17 00:00:00 2001 From: Centimo Date: Sun, 10 May 2026 19:17:16 +0300 Subject: [PATCH 8/8] Restore legacy with_() setters as [[deprecated]] shims The with<>() unification removed per-field setters from csv::Settings and parquet::Settings outright. Reintroduce them so existing user code keeps compiling, marked [[deprecated]] with a message that points at the new with<&Settings::field>(value) / with<"field">(value) API. The repeated attribute is generated by a small RFL_DEPRECATED_WITH(name) macro placed in a new internal header (rfl/internal/deprecated_with.hpp) shared by both Settings files. --- include/rfl/csv/Settings.hpp | 55 ++++++++++++++++++++++++ include/rfl/internal/deprecated_with.hpp | 12 ++++++ include/rfl/parquet/Settings.hpp | 11 +++++ 3 files changed, 78 insertions(+) create mode 100644 include/rfl/internal/deprecated_with.hpp diff --git a/include/rfl/csv/Settings.hpp b/include/rfl/csv/Settings.hpp index 137d3bd0..bf9573a4 100644 --- a/include/rfl/csv/Settings.hpp +++ b/include/rfl/csv/Settings.hpp @@ -5,6 +5,7 @@ #include #include "../Settings.hpp" +#include "../internal/deprecated_with.hpp" namespace rfl::csv { @@ -47,6 +48,60 @@ struct Settings { const bool ignore_empty_lines = true; RFL_SETTINGS_OPS(Settings) + + RFL_DEPRECATED_WITH(batch_size) + Settings with_batch_size(const int32_t _batch_size) const noexcept { + return replace(*this, make_field<"batch_size">(_batch_size)); + } + + RFL_DEPRECATED_WITH(delimiter) + Settings with_delimiter(const char _delimiter) const noexcept { + return replace(*this, make_field<"delimiter">(_delimiter)); + } + + RFL_DEPRECATED_WITH(quoting) + Settings with_quoting(const bool _quoting) const noexcept { + return replace(*this, make_field<"quoting">(_quoting)); + } + + RFL_DEPRECATED_WITH(quote_char) + Settings with_quote_char(const char _quote_char) const noexcept { + return replace(*this, make_field<"quote_char">(_quote_char)); + } + + RFL_DEPRECATED_WITH(null_string) + Settings with_null_string(const std::string& _null_string) const noexcept { + return replace(*this, make_field<"null_string">(_null_string)); + } + + RFL_DEPRECATED_WITH(double_quote) + Settings with_double_quote(const bool _double_quote) const noexcept { + return replace(*this, make_field<"double_quote">(_double_quote)); + } + + RFL_DEPRECATED_WITH(escaping) + Settings with_escaping(const bool _escaping) const noexcept { + return replace(*this, make_field<"escaping">(_escaping)); + } + + RFL_DEPRECATED_WITH(escape_char) + Settings with_escape_char(const char _escape_char) const noexcept { + return replace(*this, make_field<"escape_char">(_escape_char)); + } + + RFL_DEPRECATED_WITH(newlines_in_values) + Settings with_newlines_in_values( + const bool _newlines_in_values) const noexcept { + return replace(*this, + make_field<"newlines_in_values">(_newlines_in_values)); + } + + RFL_DEPRECATED_WITH(ignore_empty_lines) + Settings with_ignore_empty_lines( + const bool _ignore_empty_lines) const noexcept { + return replace(*this, + make_field<"ignore_empty_lines">(_ignore_empty_lines)); + } }; } // namespace rfl::csv diff --git a/include/rfl/internal/deprecated_with.hpp b/include/rfl/internal/deprecated_with.hpp new file mode 100644 index 00000000..e4ebb739 --- /dev/null +++ b/include/rfl/internal/deprecated_with.hpp @@ -0,0 +1,12 @@ +#ifndef RFL_INTERNAL_DEPRECATED_WITH_HPP_ +#define RFL_INTERNAL_DEPRECATED_WITH_HPP_ + +/// Marks a legacy with_() method as deprecated, pointing at the +/// unified RFL_SETTINGS_OPS-generated with<&T::field>() / with<"field">() +/// API. Used in csv::Settings and parquet::Settings to keep the old +/// per-field setter names working for existing users. +#define RFL_DEPRECATED_WITH(field_name) \ + [[deprecated("Use .with<&Settings::" #field_name ">(value) or " \ + ".with<\"" #field_name "\">(value) instead.")]] + +#endif diff --git a/include/rfl/parquet/Settings.hpp b/include/rfl/parquet/Settings.hpp index 78b2cd0e..026b7f7d 100644 --- a/include/rfl/parquet/Settings.hpp +++ b/include/rfl/parquet/Settings.hpp @@ -5,6 +5,7 @@ #include #include "../Settings.hpp" +#include "../internal/deprecated_with.hpp" namespace rfl::parquet { @@ -18,6 +19,16 @@ struct Settings { const Compression compression = Compression::SNAPPY; RFL_SETTINGS_OPS(Settings) + + RFL_DEPRECATED_WITH(chunksize) + Settings with_chunksize(const size_t _chunksize) const noexcept { + return replace(*this, make_field<"chunksize">(_chunksize)); + } + + RFL_DEPRECATED_WITH(compression) + Settings with_compression(const Compression _compression) const noexcept { + return replace(*this, make_field<"compression">(_compression)); + } }; } // namespace rfl::parquet