This is definitely possible, although you'll need a bit of macro boilerplate if you want to avoid typing out all the combinations.
You can use Recursive Macros to automatically generate all the combinations of a, b & c for you, so you don't have to type them out individually - this might not be necessary for vec3's (due to only 27 possible swizzles), but it'll definitly save you a lot of typing for vec4's with have 256 possible swizzles. (or even higher dimension vecs)
The usage would look like this:
godbolt
#define GEN_SWIZZLE_VEC3(a, b, c) \
inline vec3 a##b##c() const { \
return {a, b, c}; \
}
template<class T>
struct vec3 {
T x, y, z;
FOR_EACH_COMBINATION(
GEN_SWIZZLE_VEC3,
(x, y, z), // possible values for a in GEN_SWIZZLE_VEC3
(x, y, z), // possible values for b in GEN_SWIZZLE_VEC3
(x, y, z) // possible values for c in GEN_SWIZZLE_VEC3
)
};
#undef GEN_SWIZZLE_VEC3
#define GEN_SWIZZLE_VEC4(a, b, c, d) \
inline vec4 a##b##c##d() const { \
return {a, b, c, d}; \
}
template<class T>
struct vec4 {
T x, y, z, w;
FOR_EACH_COMBINATION(
GEN_SWIZZLE_VEC4,
(x, y, z, w), // possible values for a in GEN_SWIZZLE_VEC4
(x, y, z, w), // possible values for b in GEN_SWIZZLE_VEC4
(x, y, z, w), // possible values for c in GEN_SWIZZLE_VEC4
(x, y, z, w) // possible values for d in GEN_SWIZZLE_VEC4
)
};
#undef GEN_SWIZZLE_VEC4
which would expand to:
template<class T>
struct vec3 {
T x, y, z;
inline vec3 xxx() const { return {x, x, x}; }
inline vec3 xxy() const { return {x, x, y}; }
inline vec3 xxz() const { return {x, x, z}; }
inline vec3 xyx() const { return {x, y, x}; }
/* ... */
inline vec3 zzx() const { return {z, z, x}; }
inline vec3 zzy() const { return {z, z, y}; }
inline vec3 zzz() const { return {z, z, z}; }
};
template<class T>
struct vec4 {
T x, y, z, w;
inline vec4 xxxx() const { return {x, x, x, x}; }
inline vec4 xxxy() const { return {x, x, x, y}; }
inline vec4 xxxz() const { return {x, x, x, z}; }
inline vec4 xxxw() const { return {x, x, x, w}; }
inline vec4 xxyx() const { return {x, x, y, x}; }
inline vec4 xxyy() const { return {x, x, y, y}; }
/* ... */
inline vec4 wwzz() const { return {w, w, z, z}; }
inline vec4 wwzw() const { return {w, w, z, w}; }
inline vec4 wwwx() const { return {w, w, w, x}; }
inline vec4 wwwy() const { return {w, w, w, y}; }
inline vec4 wwwz() const { return {w, w, w, z}; }
inline vec4 wwww() const { return {w, w, w, w}; }
};
For this implementation i've used the new __VA_OPT__
token that has been introduced with C++20. While it's not strictly necessary for recursive macros, it makes implementing them alot easier.
Here's the full code of FOR_EACH_COMBINATION
(for an in-depth explanation see here):
godbolt
// Simple concat
// example: CONCAT(foo,bar) => foobar
#define CONCAT(a, b) CONCAT_IMPL(a, b)
#define CONCAT_IMPL(a, b) a##b
// returns the first argument
// example: VAARGS_HEAD(1,2,3) => 1
#define VAARGS_HEAD(head, ...) head
// returns all arguments except the first one
// example: VAARGS_TAIL(1,2,3) => 2,3
#define VAARGS_TAIL(head, ...) __VA_ARGS__
// basic preprocessor if
// examples:
// - IIF(1)(a,b) => a
// - IIF(0)(a,b) => b
#define IIF(value) CONCAT(IIF_,value)
#define IIF_1(true_, false_) true_
#define IIF_0(true_, false_) false_
// evaluates to 1 if it has been called with at least 1 argument, 0 otherwise
// examples:
// - HAS_VAARGS(1,2) => 1
// - HAS_VAARGS() => 0
#define HAS_VAARGS(...) VAARGS_HEAD(__VA_OPT__(1,) 0)
// forces the preprocessor to repeatedly scan an expression
// this definition forces a total of 86 scans, but can easily extended
// by adding more EXPAND*() macros (each additional one more than
// quadruples the amount of scans)
// examples:
// - CONCAT DELAY() (a,b) => CONCAT (a,b)
// - EXPAND(CONCAT DELAY() (a,b)) => ab
#define EXPAND(...) EXPAND1(EXPAND1(EXPAND1(EXPAND1(__VA_ARGS__))))
#define EXPAND1(...) EXPAND2(EXPAND2(EXPAND2(EXPAND2(__VA_ARGS__))))
#define EXPAND2(...) EXPAND3(EXPAND3(EXPAND3(EXPAND3(__VA_ARGS__))))
#define EXPAND3(...) __VA_ARGS__
// evaluates to nothing, but requires an additional preprocessor scan.
// this can be used to delay macro evaluations.
// examples:
// - CONCAT(a,b) => ab
// - CONCAT DELAY() (a,b) => a DELAY_IMPL_NOTHING () b
// - EXPAND(CONCAT DELAY() (a,b)) => ab
#define DELAY DELAY_IMPL_NOTHING
#define DELAY_IMPL_NOTHING()
// discards all arguments, evaluates to nothing
#define SWALLOW(...)
// appends an element to a tuple
// examples:
// - TUPLE_APPEND((a,b), c) => (a,b,c)
// - TUPLE_APPEND((), a) => (a)
#define TUPLE_APPEND(tuple, el) (TUPLE_APPEND_IMPL_UNPACK tuple el)
#define TUPLE_APPEND_IMPL_UNPACK(...) __VA_ARGS__ __VA_OPT__(,)
// if __VA_ARGS__ is empty then it expands to fn(args);
// otherwise it'll expand to FOR_EACH_COMBINATION_IMPL_RECURSE(fn, args, __VA_ARGS__)
#define FOR_EACH_COMBINATION_IMPL(fn, args, ...) \
IIF(HAS_VAARGS(__VA_ARGS__))( \
FOR_EACH_COMBINATION_IMPL_RECURSE, \
FOR_EACH_COMBINATION_IMPL_CALL \
)(fn, args __VA_OPT__(, __VA_ARGS__))
// evaluates the user-provided function-like macro fn with arguments args.
// example: FOR_EACH_IMPL_CALL(foo, (1,2)) => foo(1,2)
#define FOR_EACH_COMBINATION_IMPL_CALL(fn, args) \
fn args
// if tuple has at least 1 element it calls FOR_EACH_COMBINATION_IMPL_RECURSE_APPLY;
// otherwise it stops recursion.
// examples:
// - FOR_EACH_COMBINATION_IMPL_RECURSE(fn, (), (a, b))
// => FOR_EACH_COMBINATION_IMPL_RECURSE_APPLY(fn, (), (a,b))
// - FOR_EACH_COMBINATION_IMPL_RECURSE(fn, (), ())
// =>
#define FOR_EACH_COMBINATION_IMPL_RECURSE(fn, args, tuple, ...) \
IIF(HAS_VAARGS tuple)( \
FOR_EACH_COMBINATION_IMPL_RECURSE_APPLY, \
SWALLOW \
) DELAY() ( \
fn, args, tuple __VA_OPT__(, __VA_ARGS__) \
)
// calls FOR_EACH_COMBINATION_IMPL twice;
// once with the first element of tuple appended to args,
// and a second time with the first element of tuple removed.
// examples:
// - FOR_EACH_COMBINATION_IMPL_RECURSE_APPLY(fn, (), (a,b), (c,d))
// => FOR_EACH_COMBINATION_IMPL(fn, (a), (c,d))
// FOR_EACH_COMBINATION_IMPL(fn, (), (b), (c,d))
#define FOR_EACH_COMBINATION_IMPL_RECURSE_APPLY(fn, args, tuple, ...) \
FOR_EACH_COMBINATION_IMPL DELAY() ( \
fn, \
TUPLE_APPEND(args, VAARGS_HEAD tuple) \
__VA_OPT__(, __VA_ARGS__) \
) \
\
FOR_EACH_COMBINATION_IMPL DELAY() ( \
fn, \
args, \
(VAARGS_TAIL tuple) \
__VA_OPT__(, __VA_ARGS__) \
)
// takes a function-like macro (fn) and an arbitrary amount of tuples.
// the tuples can be of any size (only constrained by the number
// of expansions provided by the EXPAND macro)
// fn will be evaluated for each possible combination from the tuples.
// examples:
// - FOR_EACH_COMBINATION(foo, (a,b))
// => foo(a) foo(b)
// - FOR_EACH_COMBINATION(foo, (a,b), (1,2))
// => foo(a, 1) foo(a, 2) foo(b, 1) foo(b, 2)
#define FOR_EACH_COMBINATION(fn, ...) \
EXPAND( \
FOR_EACH_COMBINATION_IMPL( \
fn, \
() \
__VA_OPT__(, __VA_ARGS__) \
) \
)
Additionally the FOR_EACH_COMBINATION
macro can be used for all other kinds of shenanigans, like auto-generating explicit specializations of templates, or generating an array of all possible 8-bit patterns:
template<class T, T val>
struct value_holder;
#define GEN(type, val) \
template<> \
struct value_holder<type, val> { \
static constexpr type value = val; \
};
// generates specializations like:
// template<> struct value_holder<short, 0> { static constexpr short value = 0; };
// template<> struct value_holder<short, 1> { static constexpr short value = 1; };
// ...
// template<> struct value_holder<long, 9> { static constexpr long value = 9; };
// template<> struct value_holder<long, 10> { static constexpr long value = 10; };
FOR_EACH_COMBINATION(
GEN,
(short, int, long),
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
)
#undef GEN
// all possible arrangements of 8 bits, in order.
// all8BitPatterns[1] == {0,0,0,0,0,0,0,1}
// all8BitPatterns[4] == {0,0,0,0,0,1,0,0}
// all8BitPatterns[9] == {0,0,0,0,1,0,0,1}
// etc..
#define GEN(b1,b2,b3,b4,b5,b6,b7,b8) {b1,b2,b3,b4,b5,b6,b7,b8},
unsigned char all8BitPatterns[256][8] = {
FOR_EACH_COMBINATION(
GEN,
(0, 1), (0, 1), (0, 1), (0, 1),
(0, 1), (0, 1), (0, 1), (0, 1)
)
};
#undef GEN
Here are a few good reads about recursive macros if you want to read more about them:
Other references: