Reputation: 47
For example, I have this code:
#define LIST_A \
Element1 \
Element2 \
Element3
#define LIST_B \
Element4 \
Element5
// I'd like to perform some operations on tuples of LIST_A * LIST_B
// That is a set of tuples {(1, 4), (1, 5), (2, 4), (2, 5), (3, 4), (3, 5)}
The real problem I'm facing is that I have some C++ template functions like:
// Left, Right, Result can be any numeric types
template <typename Left, typename Right, typename Result>
struct Function
{
void apply(const Left & lhs, const Right & rhs, Result & res);
};
And I'd like to exhaust the specialization space of Function
.
For example I have following input data type:
#define LEFT_TYPE_LIST\
int32_t \
int64_t
#define RIGHT_TYPE_LIST\
uint32_t \
uint64_t
I want to apply LEFT_TYPE_LIST
and RIGHT_TYPE_LIST
to Function<Left, Right, Result>
.
Is there a practical way to implement this?
Upvotes: 1
Views: 336
Reputation: 218148
Without macro, you might do something like:
template <typename T> struct tag { using type = T; };
using LEFT_TYPE_LIST = std::tuple<tag<int32_t>, tag<int64_t>>;
using RIGHT_TYPE_LIST = std::tuple<tag<uint32_t, tag<int64_t>>;
and then
std::apply([](auto... lefts){
auto f = [](auto left){
std::apply([left](auto... rights){
(Function<typename decltype(left)::type,
typename decltype(rights)::type>(),
...);
}, RIGHT_TYPE_LIST{});
};
(f(lefts), ...);
}, LEFT_TYPE_LIST{});
Upvotes: 1
Reputation: 29126
If you don't mind the awkward syntax, you can use X macros:
#define TYPES1(WITH, _) \
_(WITH, Apple) \
_(WITH, Orange) \
_(WITH, Pear)
#define TYPES2(WITH, _) \
_(WITH, Apple) \
_(WITH, Kumquat)
Now you can define macros like this:
#define CMP(A, B) compare(A, B);
#define COMPARE(W, T) W(T, CMP)
and apply lists to each other (but unfortunalely not to themselves):
TYPES1(TYPES2, COMPARE)
TYPES2(TYPES1, COMPARE)
How does this work? The _
parameter passed to the macro list is a macro itself and it expands according to its arguments. Here, the _
macro must be a macro that takes two parameters.
This is usually done to establish different contexts with different macros, for example to have the argument expand to an enum
constant once and to a stringized names later. Here, the different contexts are the levels of invocation. The WITH
parameter is either the first object (at the lowest level) or anoter X macro.
For completeness sake: Macros will get you only so far. Sometimes it is better to prepare such data externally by a script or program that creates a file you can include. You can make that "code generation" part of your build process. If you have a lot of such macros or if the macros get bizarre, that might be a better option.
Upvotes: 5