如何减少二维 _Generic 中第二个参数的扩展次数?
How to reduce number of expansions of second argument in 2-dimensional _Generic?
我有以下代码:
int add_ii(int a, int b) { return a + b; }
unsigned add_iu(int a, unsigned b) { return a + b; }
unsigned add_ui(unsigned a, int b) { return a + b; }
unsigned add_uu(unsigned a, unsigned b) { return a + b; }
#define add(LEFT, RIGHT) \
_Generic(LEFT \
,int: _Generic(RIGHT \
,int: add_ii \
,unsigned: add_iu \
) \
,unsigned: _Generic(RIGHT \
,int: add_ui \
,unsigned: add_uu \
) \
)(LEFT, RIGHT)
int main() {
return add(1, add(2, add(3, 4)));
}
问题是每个 _Generic
个案例都扩展了 RIGHT
。上面RIGHT
使用了3次,所以add(2, add(3, 4))
完全展开了3次,所以add(3, 4)
展开了9次。随着每次嵌套使用和在 _Generic
中处理的每个额外类型,该数字呈指数增长。这本身不是问题 - 但它会导致预处理器生成 非常大的 输出,显着 停止编译过程并导致 非常高 编译内存使用率。
有什么方法可以写二维的_Generic
,让RIGHT
不至于每次都展开每个类型?
用 RIGHT
切换 LEFT
当然不是一个选项,也不会做太多 - LEFT
然后会扩展那么多次。
我知道我可以使用 GNU 扩展,但我的想法是我不想使用它们:
#define add(LEFT, RIGHT0) ({
__auto_type LEFT = LEFT0;
__auto_type RIGHT = RIGHT0;
_Generic(LEFT, .....)(LEFT, RIGHT)
})
我可以写一种映射,但是会删除 return 类型信息并要求将所有函数类型强制转换为通用类型,或者使用一些不安全的 va_arg
:
unsigned add_ii_adaptor(unsigned a, unsigned b) { return add_ii(a, b); }
unsigned add_iu_adaptor(unsigned a, unsigned b) { return add_iu(a, b); }
unsigned add_ui_adaptor(unsigned a, unsigned b) { return add_ui(a, b); }
static unsigned (*const funcarr[])(unsigned a, unsigned b) = {
add_ii_adaptor,
add_iu_adaptor,
add_ui_adaptor,
add_uu,
};
#define typeidx(x) _Generic((x), int: 0, unsigned 1)
#define add(LEFT, RIGHT) \
funcarr[typeidx(LEFT) << 1 | typeidx(RIGHT)](LEFT, RIGHT)
有没有我忽略的方法?
背景:However, what is the ultimate point here?
我正在实施 Integer safety n2792 without GNU extensions here。我想支持 26 种类型,ckd_add(x, y, z)
使它有 17576 种类型组合。 (这可以减少,但仍然如此)。在上面的示例中,return 类型是操作数的提升类型,因此 unsigned + int = unsigned
,但例如它可能是 long + char = long *or unsigned long!*
.
可以使用 _Generic
将元组映射到整数。您需要一个由整数参数化的类型,而 C 提供了这样一个系列……数组。
但是,数组不能用作分派参数,因为数组会衰减为指针。但是,指向数组的指针 不会 衰减。
只需使用类似于 typeidx
的宏来计算数组的大小,并将 is 用作新数组类型的大小。添加 1
因为 C 禁止零大小数组。
接下来使用复合文字形成一个指向它的指针。
E.q。 (int(*)[3]) { 0 }
。
最后,使用这个文字的类型来派发一个合适的函数指针。
#define TYPE_TO_NUM(X) _Generic((X), int: 0, unsigned: 1)
#define add(LEFT, RIGHT) \
_Generic( \
(int(*)[1 + 2 * TYPE_TO_NUM(LEFT) + TYPE_TO_NUM(RIGHT)]) { 0 } \
,int(*)[1]: add_ii \
,int(*)[2]: add_iu \
,int(*)[3]: add_ui \
,int(*)[4]: add_uu \
)(LEFT, RIGHT)
由于扩展了 LEFT
和 RIGHT
两次,解决方案仍然具有指数复杂度,但比原来的快得多。
我有以下代码:
int add_ii(int a, int b) { return a + b; }
unsigned add_iu(int a, unsigned b) { return a + b; }
unsigned add_ui(unsigned a, int b) { return a + b; }
unsigned add_uu(unsigned a, unsigned b) { return a + b; }
#define add(LEFT, RIGHT) \
_Generic(LEFT \
,int: _Generic(RIGHT \
,int: add_ii \
,unsigned: add_iu \
) \
,unsigned: _Generic(RIGHT \
,int: add_ui \
,unsigned: add_uu \
) \
)(LEFT, RIGHT)
int main() {
return add(1, add(2, add(3, 4)));
}
问题是每个 _Generic
个案例都扩展了 RIGHT
。上面RIGHT
使用了3次,所以add(2, add(3, 4))
完全展开了3次,所以add(3, 4)
展开了9次。随着每次嵌套使用和在 _Generic
中处理的每个额外类型,该数字呈指数增长。这本身不是问题 - 但它会导致预处理器生成 非常大的 输出,显着 停止编译过程并导致 非常高 编译内存使用率。
有什么方法可以写二维的_Generic
,让RIGHT
不至于每次都展开每个类型?
用 RIGHT
切换 LEFT
当然不是一个选项,也不会做太多 - LEFT
然后会扩展那么多次。
我知道我可以使用 GNU 扩展,但我的想法是我不想使用它们:
#define add(LEFT, RIGHT0) ({
__auto_type LEFT = LEFT0;
__auto_type RIGHT = RIGHT0;
_Generic(LEFT, .....)(LEFT, RIGHT)
})
我可以写一种映射,但是会删除 return 类型信息并要求将所有函数类型强制转换为通用类型,或者使用一些不安全的 va_arg
:
unsigned add_ii_adaptor(unsigned a, unsigned b) { return add_ii(a, b); }
unsigned add_iu_adaptor(unsigned a, unsigned b) { return add_iu(a, b); }
unsigned add_ui_adaptor(unsigned a, unsigned b) { return add_ui(a, b); }
static unsigned (*const funcarr[])(unsigned a, unsigned b) = {
add_ii_adaptor,
add_iu_adaptor,
add_ui_adaptor,
add_uu,
};
#define typeidx(x) _Generic((x), int: 0, unsigned 1)
#define add(LEFT, RIGHT) \
funcarr[typeidx(LEFT) << 1 | typeidx(RIGHT)](LEFT, RIGHT)
有没有我忽略的方法?
背景:However, what is the ultimate point here?
我正在实施 Integer safety n2792 without GNU extensions here。我想支持 26 种类型,ckd_add(x, y, z)
使它有 17576 种类型组合。 (这可以减少,但仍然如此)。在上面的示例中,return 类型是操作数的提升类型,因此 unsigned + int = unsigned
,但例如它可能是 long + char = long *or unsigned long!*
.
可以使用 _Generic
将元组映射到整数。您需要一个由整数参数化的类型,而 C 提供了这样一个系列……数组。
但是,数组不能用作分派参数,因为数组会衰减为指针。但是,指向数组的指针 不会 衰减。
只需使用类似于 typeidx
的宏来计算数组的大小,并将 is 用作新数组类型的大小。添加 1
因为 C 禁止零大小数组。
接下来使用复合文字形成一个指向它的指针。
E.q。 (int(*)[3]) { 0 }
。
最后,使用这个文字的类型来派发一个合适的函数指针。
#define TYPE_TO_NUM(X) _Generic((X), int: 0, unsigned: 1)
#define add(LEFT, RIGHT) \
_Generic( \
(int(*)[1 + 2 * TYPE_TO_NUM(LEFT) + TYPE_TO_NUM(RIGHT)]) { 0 } \
,int(*)[1]: add_ii \
,int(*)[2]: add_iu \
,int(*)[3]: add_ui \
,int(*)[4]: add_uu \
)(LEFT, RIGHT)
由于扩展了 LEFT
和 RIGHT
两次,解决方案仍然具有指数复杂度,但比原来的快得多。