EDIT2: Figured it out. See my answer below.
Some background:
I am using an SQL library that returns query results as tuples. For each DB statement I write:
- An SQL query that includes a list of field names
- A tuple to receive the results of each row
- an
enum
with symbolic constants to use withstd::get<>
to access the individual fields.
This is an error prone process (especially if that I'd like to automate using a macro, such that for the following table and statement:
CREATE TABLE data (
id integer NOT NULL GENERATED ALWAYS AS IDENTITY,
field1 integer NOT NULL,
field2 bigint NOT NULL,
field3 varchar(100) NOT NULL
field4 smallint);
SELECT field1, field2, field3, field4
FROM data WHERE id = $1
I could write something like:
GENERATE_QUERY(
(FIELD1, int32_t),
(FIELD2, int64_t),
(FIELD3, std::string),
(FIELD4, std::optional<int16_t>)
)
To have the preprocessor generate something like this:
using RowType = std::tuple<int32_t, int64_t, std::string, std::optional<int16_t>>;
enum FIELDS { FIELD1, FIELD2, FIELD3, FIELD4 };
static constexpr auto fieldList = "FIELD1, FIELD2, FIELD3, FIELD4";
// or the equivalent "FIELD1" "," "FIELD2" "," "FIELD3" "," "FIELD4"
And then, this will work:
auto query = "select "s + fieldList + " from data where id = $1";
auto row = exec(query, i).asTuple<RowType>()
do_something_with(std::get<FILED3>(row));
I tried to use the FOR_EACH
macro from David Mazières's blog:
#define PARENS ()
#define EXPAND(arg) EXPAND1(EXPAND1(EXPAND1(EXPAND1(arg))))
#define EXPAND1(arg) EXPAND2(EXPAND2(EXPAND2(EXPAND2(arg))))
#define EXPAND2(arg) EXPAND3(EXPAND3(EXPAND3(EXPAND3(arg))))
#define EXPAND3(arg) EXPAND4(EXPAND4(EXPAND4(EXPAND4(arg))))
#define EXPAND4(arg) arg
#define FOR_EACH(macro, ...) \
__VA_OPT__(EXPAND(FOR_EACH_HELPER(macro, __VA_ARGS__)))
#define FOR_EACH_HELPER(macro, arg, ...) \
macro(arg) __VA_OPT__(FOR_EACH_AGAIN PARENS (macro, __VA_ARGS__))
#define FOR_EACH_AGAIN() FOR_EACH_HELPER
and the following got me close:
#define ARG1_(arg, ...) arg
#define ARG1(arg) ARG1_ arg
#define ARG2_(_1, arg, ...) arg
#define ARG2(arg) ARG2_ arg
#define ARG_1_STR(arg, ...) #arg
#define ARG1_STR(arg) ARG_1_STR arg
#define GENERATE_QUERY(...) \
enum Columns { FOR_EACH(ARG1, __VA_ARGS__) }; \
using Tuple = std::tuple<FOR_EACH(ARG2, __VA_ARGS__)>; \
static constexpr auto query = FOR_EACH(ARG1_STR, __VA_ARGS__);
The result this gets me is:
enum Columns { FIELD1 FIELD2 FIELD3 FIELD4 };
using Tuple = std::tuple<int32_t int64_t std::string std::optional<int16_t> >;
static constexpr auto query = "FIELD1" "FIELD2" "FIELD3" "FIELD4";
The only thing missing is the commas. Unfortunately I was not able to modify the macros to accept a delimiter to be emitted between the elements.
So my question is:
How do I modify the FOR_EACH
macros to add a user-specified delimiter (,
or ","
in this case) to insert between the elements? Or failing that, what construct should I use instead?
(Generalizing the ARGn
macros would be nice but it's secondary).
EDIT: A failed attempt:
The following modifications did not work:
#define FOR_EACH(macro, delim, ...) \
__VA_OPT__(EXPAND(FOR_EACH_HELPER(macro, delim, __VA_ARGS__)))
#define FOR_EACH_HELPER(macro, delim, arg, ...) \
macro(arg) __VA_OPT__( delim FOR_EACH_AGAIN PARENS (macro, delim, __VA_ARGS__))
#define COMMA() ,
#define COMMA_STR() ","
#define GENERATE_QUERY(...) \
enum Columns { FOR_EACH(ARG1, COMMA, __VA_ARGS__) }; \
using Tuple = std::tuple<FOR_EACH(ARG2, COMMA, __VA_ARGS__)>; \
static constexpr auto query = FOR_EACH(ARG1_STR, COMMA_STR, __VA_ARGS__);
It inserted the unexpanded COMMA
and COMMA_STR
between the arguments. Almost...