我现在工作的代码库已经过大量优化。本着这种精神,函数通常采用
bool
或 enum
模板参数,然后使用 if constexpr
在它们上进行分支。关键是,我们可以确保在处理数百万个项目的紧密循环中,该函数实际上只包含实际需要的代码。例如:
template<bool use_filter> void foo(Items& items) {
if constexpr (use_filter) {
if (!filter(items)) return;
}
process(items);
}
显然,实际功能要复杂得多,但上面的示例演示了如何使用它。问题是,现在我们的代码如下所示:
if (query.use_filter()) {
foo<true>(items);
} else {
foo<false>(items);
}
实际上更糟糕,因为我们经常有多个模板参数,因此 if 森林变得非常大。有趣的是我真正想写的是
foo<query.use_filter()>(items)
。也就是说,以某种方式告诉 C++ 通过在后台自动实现分支来将运行时值转换为模板参数。是的,这是代码爆炸,但我们已经在这样做了。可以使用宏来实现这一点(在某种程度上),但它很快就会变得非常难看。最好不要使用宏。
问题:如果没有宏,这可能吗?我知道,用宏是可行的。
std::variant
和 std::visit
:
std::variant<std::true_type, std::false_type> as_bool_type(bool b)
{
if (b) {
return std::true_type{};
} else {
return std::false_type{};
}
}
然后
std::visit([&](auto b/* possibly other param */){ foo<b>(items) },
as_bool_type(query.use_filter()) /* possibly other variant..*/);
您可以为值创建类似
std::visit
的内容。我称之为“编译时提升”,它在 Boost.Mp11 中为 mp_with_index
(用于 std::size_t
,但您可以轻松地与 bool
一起使用它):
// Boost version
mp_with_index<2>(query.use_filter(), [&](auto use_filter) {
foo(items);
});
// No boost version
template<typename F>
constexpr decltype(auto) constexpr_promote(bool b, F&& f) {
if (b)
return std::forward<F>(f)(std::true_type{});
return std::forward<F>(f)(std::false_type{});
}
constexpr_promote(query.use_filter(), [&](auto use_filter) {
foo(items);
});
当然!不过,老实说,如果您稍微重写一下模板函数,会更容易。
template<bool b>
using bool_constant = std::integral_constant<bool, b>
template<bool use_filter>
void foo(bool_constant<use_filter>, std::Items& items)
这有助于更直接地调用
foo
,而不必将参数作为模板和非模板参数传递。
然后我们写这个玩具:
using kbool = std::variant< bool_constant<false>, bool_constant<true> >;
a
kbool
有两种可能的状态,对应于 false
和 true
。
kbool evaluate( bool b ) {
if (b) return bool_constant<true>{};
else return bool_constant<false>{};
}
std::visit( [&](auto kb) {
foo( kb, items );
}, evaluate(query.use_filter()) );
我们将运行时 bool 转换为 false/true 上的 std::variant,然后使用
foo
将其传递给 std::visit
。
你可以随心所欲地装饰它。
底层类型是
bool
是可选的;它可以是任何有限枚举(一定范围内的整数或枚举)。
运行时的
kbool
实际上是一个整数,如果为 false,则值为 0
;如果为 true,则值为 1
。我们只是将其打扮成可以让我们做类似std::visit
之类的事情。
您甚至可以将
foo
包裹起来,这样它就需要一个 kbool
并在内部执行 std::visit
;这个包装器甚至可以直接调度 bool_constant
而无需 std::visit
ing。
然后
foo(query.use_filter(), items);
成为使用点的使用方式。