C++(gcc)预处理器宏:自动函数生成- OpenGL着色器“Swizzle”语法

niknxzdl  于 2022-11-12  发布在  其他
关注(0)|答案(3)|浏览(173)

让我先声明我不知道我的目标是否可能,如果可能,我不知道如何找到信息。
OpenGL着色语言允许一种称为swizzeling的语法。
如果有一个向量

v {x, y, z}

可以通过以下方式构建新的向量

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

总共有3 * 3 * 3 = 27个可能的选项。
我想在我自己的矢量库中实现这种特性。
以下是此类函数的一个示例:

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

然后我可以编写另外26个函数来说明所有可能的选项,但这似乎是我应该能够使用宏来完成的事情。

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

其中,#A、#B和#C是3个单个字符,编译器可使用xyz选项对其进行扩展。
这样的事情在gcc/g++中可能吗?

de90aj5v

de90aj5v1#

#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)
...

有关##运算符的更多信息,请搜索“标记粘贴”。

8e2ybdfx

8e2ybdfx2#

您可以定义

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

以致

make_method(a,b,c)

扩展到

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

现在你只需要像这样列出所有27种组合:

make_method(x,x,x);
make_method(x,x,y);
make_method(x,x,z);
make_method(x,y,x);
// ....
busg9geu

busg9geu3#

这绝对是可能的,尽管如果你想避免输入所有的组合,你需要一点宏样板。
你可以使用递归宏自动生成所有的a,b和c的组合,这样你就不必单独输入它们了--这对于vec 3来说可能没有必要(因为只有27种可能的混合),但是对于vec 4来说,它肯定会为你节省大量的输入,因为vec 4有256种可能的混合。(甚至更高维的vecs)
用法如下所示:
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

其范围将扩大到:

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}; }
};

对于这个实现,我使用了C++20中引入的新的__VA_OPT__标记。虽然对于递归宏来说,它不是严格必需的,但它使实现它们变得容易得多。
下面是FOR_EACH_COMBINATION的完整代码(用于深入解释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__) \
    ) \
  )

此外,FOR_EACH_COMBINATION宏还可以用于所有其他类型的恶作剧,如auto-generating explicit specializations of templatesgenerating 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

如果您想了解更多有关递归宏的信息,请阅读以下几本好书:

其他参考资料:

相关问题