4

Let me start this question by stating that I don't know if what I am aiming to do is possible, and if it is, I don't know how to find the information.

The OpenGL shading language permits a syntax called swizzeling.

If one has a vector

v {x, y, z}

one can construct a new vector by doing

v.xxx, v.xxy, v.xxz, v.xyx, ... etc

There are a total of 3 * 3 * 3 = 27 possible options.

I would like to implement this kind of feature in my own vector library.

Here is one example of such a function:

vec3<T> xxx() const
{
    vec3<T> v(x, x, x);
    return v;
}

I could then write another 26 functions to account for all possible options, but this seems like something I should be able to do using a macro. For example, something like

vec3<T> (#A)(#B)(#C)() const
{
    vec3<T> v(#A, #B, #C);
    return v;
}

where #A, #B and #C are 3 single characters which the compiler expands with possible options being x, y and z.

Is such a thing possible with gcc/g++ ?

FreelanceConsultant
  • 13,167
  • 27
  • 115
  • 225
  • instead of asking "is it possible" better describe what went wrong why you tried to implement it. The answer to the question you are currently asking is "Yes". – 463035818_is_not_an_ai Jun 01 '21 at 13:51
  • @463035818_is_not_a_number I read a few pages linked here, none of which provided any info which I thought I could adapt to do what I am aiming to do https://gcc.gnu.org/onlinedocs/cpp/Macros.html – FreelanceConsultant Jun 01 '21 at 14:05
  • You could certainly use a macro to minimize the _amount_ of code duplication, but I think you're still going to have to spell out every permutation of the `xyz` by hand. So instead of 27 five-line functions, you're looking at a five/six-line macro and 27 invocations. I hope you don't need to extend this to a vector4... – Tim Randall Jun 01 '21 at 14:20
  • @TimRandall - still that is a big improvement, how can this be implemented? – FreelanceConsultant Jun 01 '21 at 14:25
  • 1
    Two things: OpenGL actually specifies swizzling for 4D vectors (x, y, z and w). And if you want to do this in C++, check glm, it already does this and it's the de facto C/C++ library for OpenGL/Vulkan math. – Blindy Jun 01 '21 at 14:35

3 Answers3

3
#define SWIZZLE(a,b,c)\
vec3<T> a##b##c() const\
{\
    vec3<T> v(a, b, c);\
    return v;\
}

SWIZZLE(x,x,y)
SWIZZLE(x,x,y)
SWIZZLE(x,x,z)
...

For more info on the ## operator, search for "token pasting".

Tim Randall
  • 4,040
  • 1
  • 17
  • 39
0

You can define

#define make_method(x,y,z) vec3<T> x##y##z () const { return { x , y, z}; }

Such that

make_method(a,b,c)

expands to

vec3<T> abc () const { return { a , b, c}; }

Now you just have to list all 27 combinations like this:

make_method(x,x,x);
make_method(x,x,y);
make_method(x,x,z);
make_method(x,y,x);
// ....
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
0

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:

Turtlefight
  • 9,420
  • 2
  • 23
  • 40