解压可变参数模板时避免 "recursive" 函数调用,直到运行时条件
Avoid "recursive" function calls while unpacking variadic templates until runtime condition
基本上,我想在参数包中找到满足某些运行时条件的模板。直觉上,我只想遍历参数包的实例化并找到第一个满足条件的实例。我当前的简化玩具实现演示了我的意思:
首先找到X
和Y
的结构满足它们的test()
struct X {
bool test(int i) {
flag = i > 10;
return flag;
}
bool flag;
std::string value = "X satisfied first";
};
struct Y {
bool test(int i) {
flag = i > 11;
return flag;
}
bool flag;
std::string value = "Y satiesfied first";
};
这个结构找到X
和Y
的第一个结构满足条件。在此示例中,它将一个整数增加到给定限制,直到其中一个结构报告其 test()
成功。
template <typename... Ts> struct FindFirst {
static std::string find_first(int limit) {
return find_first_satisfying(limit, Ts{}...);
}
static std::string find_first_satisfying(int limit, Ts... ts) {
int i = 0;
bool satisfied = false;
while (i < limit && !satisfied) {
satisfied = (ts.test(i) || ...);
i++;
}
return extract(ts...);
}
template <typename T, typename... OtherTs>
static std::string extract(T t, OtherTs... ts) {
if (t.flag) {
return t.value;
} else {
if constexpr (sizeof...(OtherTs) > 0) {
return extract(ts...);
} else {
return "Nobody satiesfied condition";
}
}
}
};
此实现生成与包中模板一样多的具有不同签名的不同 extract()
函数。它们被 "recursively" 调用并导致深度调用堆栈(取决于令人满意的结构的位置)和大字节码。
有没有一种方法可以构建一个循环(在编译时)来测试参数包的每个实例并适当地停止?
另外,关于如何简化整个结构还有其他建议吗?
我会这样写你的代码:
template <typename ... Ts>
std::string find_first_satisfying(int limit, Ts... ts)
{
for (int i = 0; i != limit; ++i) {
std::string res;
bool found = false;
([&](){ if (ts.test(i)) { found = true; res = ts.value; } return found;}() || ...);
if (found) { return res; }
}
return "Nobody satisfied condition";
}
没有。有可能在 C++23 中不会是这样,但目前还不能保证。
但是真的有问题吗?我看到的唯一问题是代码难以编写和理解。大字节码意义不大,优化器应该能够内联和优化所有内容——只有调试性能会因此受到影响(和编译时间)……除非你以一种使 optimizer/compiler 无法执行的方式编写程序内联它(通过隐藏函数体)。
P.S。您不能以某种方式将 extract 编写为运算符并使用 ...
而不是递归吗?不过,出于各种原因,我认为这是一个坏主意。 (我看到@Jarod42 在另一个答案中是通过 lambda 写的,我觉得不错。)
基本上,我想在参数包中找到满足某些运行时条件的模板。直觉上,我只想遍历参数包的实例化并找到第一个满足条件的实例。我当前的简化玩具实现演示了我的意思:
首先找到X
和Y
的结构满足它们的test()
struct X {
bool test(int i) {
flag = i > 10;
return flag;
}
bool flag;
std::string value = "X satisfied first";
};
struct Y {
bool test(int i) {
flag = i > 11;
return flag;
}
bool flag;
std::string value = "Y satiesfied first";
};
这个结构找到X
和Y
的第一个结构满足条件。在此示例中,它将一个整数增加到给定限制,直到其中一个结构报告其 test()
成功。
template <typename... Ts> struct FindFirst {
static std::string find_first(int limit) {
return find_first_satisfying(limit, Ts{}...);
}
static std::string find_first_satisfying(int limit, Ts... ts) {
int i = 0;
bool satisfied = false;
while (i < limit && !satisfied) {
satisfied = (ts.test(i) || ...);
i++;
}
return extract(ts...);
}
template <typename T, typename... OtherTs>
static std::string extract(T t, OtherTs... ts) {
if (t.flag) {
return t.value;
} else {
if constexpr (sizeof...(OtherTs) > 0) {
return extract(ts...);
} else {
return "Nobody satiesfied condition";
}
}
}
};
此实现生成与包中模板一样多的具有不同签名的不同 extract()
函数。它们被 "recursively" 调用并导致深度调用堆栈(取决于令人满意的结构的位置)和大字节码。
有没有一种方法可以构建一个循环(在编译时)来测试参数包的每个实例并适当地停止? 另外,关于如何简化整个结构还有其他建议吗?
我会这样写你的代码:
template <typename ... Ts>
std::string find_first_satisfying(int limit, Ts... ts)
{
for (int i = 0; i != limit; ++i) {
std::string res;
bool found = false;
([&](){ if (ts.test(i)) { found = true; res = ts.value; } return found;}() || ...);
if (found) { return res; }
}
return "Nobody satisfied condition";
}
没有。有可能在 C++23 中不会是这样,但目前还不能保证。
但是真的有问题吗?我看到的唯一问题是代码难以编写和理解。大字节码意义不大,优化器应该能够内联和优化所有内容——只有调试性能会因此受到影响(和编译时间)……除非你以一种使 optimizer/compiler 无法执行的方式编写程序内联它(通过隐藏函数体)。
P.S。您不能以某种方式将 extract 编写为运算符并使用 ...
而不是递归吗?不过,出于各种原因,我认为这是一个坏主意。 (我看到@Jarod42 在另一个答案中是通过 lambda 写的,我觉得不错。)