Skip to content

Commit 4821107

Browse files
Pierre-Luc GagnéCopilot
andcommitted
Add tagged_column_field alias for tag-struct style without macros
Provides a third, macro-free way to define columns using a tag struct. The SQL column name is derived at compile time from the tag type name by stripping a trailing "_tag" suffix (if present): struct price_tag {}; using price = tagged_column_field<price_tag, double>; price price_; This is exactly equivalent to column_field<"price", double>. Implementation: - Add default constructor to fixed_string<N> so it can be constructed and filled character-by-character in consteval contexts - Add detail::fixed_string_from_sv<N> consteval helper - Add detail::column_name_from_tag<Tag>() consteval function that calls extract_type_name<Tag>() and strips the "_tag" suffix - Add tagged_column_field<Tag, T> alias template in column_field.hpp - Include name_reflection.hpp in column_field.hpp for extract_type_name - Add static_assert and runtime tests in test_field_types.cpp - Update README Quick Start to document all three styles Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent b93cc5f commit 4821107

4 files changed

Lines changed: 113 additions & 8 deletions

File tree

README.md

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,20 @@ struct product {
3737
price price_;
3838
};
3939

40-
// The COLUMN_FIELD(tag, type) macro is a one-liner convenience equivalent:
41-
// struct product {
42-
// COLUMN_FIELD(id, uint32_t)
43-
// COLUMN_FIELD(sku, varchar_field<64>)
44-
// COLUMN_FIELD(name, varchar_field<255>)
45-
// COLUMN_FIELD(price, double)
46-
// };
40+
// Three equivalent styles — pick what suits your team:
41+
42+
// a) Explicit string literal (recommended — column name is right there):
43+
// using price = column_field<"price", double>;
44+
// price price_;
45+
46+
// b) Tag struct — no string literals, column name derived from tag type at
47+
// compile time (trailing "_tag" suffix is stripped automatically):
48+
// struct price_tag {};
49+
// using price = tagged_column_field<price_tag, double>;
50+
// price price_;
51+
52+
// c) COLUMN_FIELD macro — one-liner shorthand for style (a):
53+
// COLUMN_FIELD(price, double)
4754

4855
// 2. Connect and query
4956
auto db = mysql_database::connect(mysql_config{

lib/include/ds_mysql/column_field.hpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <utility>
1010

1111
#include "ds_mysql/fixed_string.hpp"
12+
#include "ds_mysql/name_reflection.hpp"
1213
#include "ds_mysql/sql_temporal.hpp"
1314
#include "ds_mysql/varchar_field.hpp"
1415

@@ -521,6 +522,68 @@ using unwrap_column_field_t = typename unwrap_column_field<T>::type;
521522

522523
} // namespace ds_mysql
523524

525+
// ===================================================================
526+
// tagged_column_field — tag-struct convenience alias
527+
// ===================================================================
528+
529+
namespace ds_mysql {
530+
531+
namespace detail {
532+
533+
/**
534+
* Construct a fixed_string<N> from a string_view at compile time.
535+
* N must equal sv.size() + 1 (to include the null terminator).
536+
*/
537+
template <std::size_t N>
538+
consteval fixed_string<N> fixed_string_from_sv(std::string_view sv) noexcept {
539+
fixed_string<N> result{};
540+
for (std::size_t i = 0; i + 1 < N; ++i)
541+
result.data[i] = sv[i];
542+
return result;
543+
}
544+
545+
/**
546+
* Derive the column name from a tag type at compile time.
547+
* Strips a trailing "_tag" suffix if present, then returns a fixed_string.
548+
*
549+
* Examples:
550+
* column_name_from_tag<id_tag>() → fixed_string{"id"}
551+
* column_name_from_tag<price_tag>() → fixed_string{"price"}
552+
* column_name_from_tag<foo>() → fixed_string{"foo"} (no suffix)
553+
*/
554+
template <typename Tag>
555+
consteval auto column_name_from_tag() noexcept {
556+
constexpr std::string_view full = extract_type_name<Tag>();
557+
constexpr std::string_view suffix = "_tag";
558+
constexpr std::string_view name = full.ends_with(suffix)
559+
? full.substr(0, full.size() - suffix.size())
560+
: full;
561+
return fixed_string_from_sv<name.size() + 1>(name);
562+
}
563+
564+
} // namespace detail
565+
566+
/**
567+
* tagged_column_field<Tag, T> — tag-struct alternative to column_field<"name", T>.
568+
*
569+
* Derive the SQL column name from a tag struct at compile time (stripping a
570+
* trailing "_tag" suffix). The result is an ordinary column_field, so it
571+
* satisfies all the same concepts and works everywhere column_field does.
572+
*
573+
* Usage:
574+
* struct price_tag {};
575+
* using price = tagged_column_field<price_tag, double>;
576+
* price price_;
577+
*
578+
* This is exactly equivalent to:
579+
* using price = column_field<"price", double>;
580+
* price price_;
581+
*/
582+
template <typename Tag, typename T>
583+
using tagged_column_field = column_field<detail::column_name_from_tag<Tag>(), T>;
584+
585+
} // namespace ds_mysql
586+
524587
/**
525588
* COLUMN_FIELD(tag, type) — convenience macro to declare a column field member.
526589
*

lib/include/ds_mysql/fixed_string.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ template <std::size_t N>
2222
struct fixed_string {
2323
char data[N]{};
2424

25+
constexpr fixed_string() noexcept = default;
26+
2527
// NOLINTNEXTLINE(google-explicit-constructor)
2628
consteval fixed_string(char const (&str)[N]) noexcept {
2729
for (std::size_t i = 0; i < N; ++i)

tests/unit/test_field_types.cpp

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,42 @@ suite<"varchar_field"> varchar_field_suite = [] {
157157
};
158158

159159
// ===================================================================
160-
// column_field with std::string value type
160+
// tagged_column_field — tag-struct alias
161161
// ===================================================================
162162

163+
namespace {
164+
struct price_tag {};
165+
struct order_id_tag {};
166+
struct no_suffix {};
167+
168+
// With _tag suffix stripped:
169+
static_assert(std::is_same_v<tagged_column_field<price_tag, double>,
170+
column_field<"price", double>>);
171+
// Multi-word tag with _tag stripped:
172+
static_assert(std::is_same_v<tagged_column_field<order_id_tag, uint32_t>,
173+
column_field<"order_id", uint32_t>>);
174+
// Tag without _tag suffix — name kept as-is:
175+
static_assert(std::is_same_v<tagged_column_field<no_suffix, int>,
176+
column_field<"no_suffix", int>>);
177+
} // namespace
178+
179+
suite<"tagged_column_field"> tagged_column_field_suite = [] {
180+
"tagged_column_field column_name matches derived tag name"_test = [] {
181+
using price = tagged_column_field<price_tag, double>;
182+
expect(price::column_name() == "price"sv);
183+
};
184+
185+
"tagged_column_field with multi-word tag"_test = [] {
186+
using order_id = tagged_column_field<order_id_tag, uint32_t>;
187+
expect(order_id::column_name() == "order_id"sv);
188+
};
189+
190+
"tagged_column_field without _tag suffix keeps full name"_test = [] {
191+
using ns = tagged_column_field<no_suffix, int>;
192+
expect(ns::column_name() == "no_suffix"sv);
193+
};
194+
};
195+
163196
namespace {
164197
using string_column = column_field<"string_col", std::string>;
165198
} // namespace

0 commit comments

Comments
 (0)