普通的 if-else
是在执行期进行条件判断与选择, 这意味着在泛型编程中无法使用
if-else
语句进行条件判断. 比如例 1 会引起编译错误
例 1: 将多个数累加并返回累加结果
template <int N, int... Ns>
auto sum()
{
if (sizeof...(Ns) == 0) // 若参数包为空, 直接返回 N
return N;
else // 否则进行递归调用
return N + sum<Ns...>();
}
// 调用
sum<1, 2, 3>();
例 1 无法编译通过.
C++17 之前的做法
C++17 之前, 必须利用多个模板才能完成例 1 的功能
例 2: C++17 之前, 将多个数累加并返回累加结果
// 只有一个模板参数时调用此模板
template<int N>
int sum()
{
return N;
}
// 模板参数 > 2 个时调用此模板
template <int N, int N2, int... Ns>
int sum()
{
return N + sum<N2, Ns...>();
}
// 调用
sum<1, 2, 3>(); // returns 6
C++17 的作法
C++17 引入 constexpr if
支持在编译期执行, 可以将之应用于泛型编程中的条件判断,
如例 3.
例 3: C++17, 将多个数累加并返回累加结果
//
template <int N, int... Ns>
auto sum()
{
if constexpr (0 == sizeof...(Ns))
return N;
else
return N + sum<Ns...>();
}
// 调用
sum<1, 2, 3>(); // returns 6
更简单的是利用 C++17 提供的新功能 “折叠表达式”, 如例 4.
例 4: 利用折叠表达式实现多个数累加并返回累加结果
//
template<typename ...Ns>
auto sum(Ns... ns) {
return (ns + ...);
}
// 调用
sum(1, 2, 3); // returns 6
更多
constexpr if
可以应用更多的场合, 比如要将数值转化为字符串. 在 C++17
之前, 需要使用 std::enable_if
来判断参数类型, 如例 4.
例 4
// 数值转换
template<typename T>
std::enable_if_t<std::is_integral<T>::value, std::string>
to_string(T t)
{
return std::to_string(t);
}
template<typename T>
std::enable_if_t<!std::is_integral<T>::value, std::string>
to_string(T t)
{
return t;
}
而在 C++17 中可以利用 constexpr if
完成相同功能, 且摒弃了冗长的
std::enable_if
语句, 不仅代码可读性好, 而且书写也更方便, 如例 5.
例 5
template<typename T>
auto to_string(T t)
{
if constexpr(std::is_integral<T>::value)
return std::to_string(t);
else
return t;
}
便是要注意在 constexpr if
中的 return 语句, 下面的写法是不对的,
应当在条件语句的分支中 return, .
template<typename T>
auto to_string(T t)
{
if constexpr(std::is_integral<T>::value)
return std::to_string(t);
// 此处需要 else
return t;
}
最后
正如许多 C++ 著作中提到的, 如果一个函数中使用了大量的 if-else
语句,
那么需要重构, 将多个条件语句转化成函数重载会更加清晰. 同理,
如果泛型函数中使用大量的 constexpr if
语句, 也许也需要考虑代码重构.