顺序执行没有太多可说的,唯一需要注意的是,类型的声明必须按照顺序,否则编译器无法识别。不按照顺序的声明,在运行时的程序是合法的,编译过程会扫描两次,第一次是找声明位置,第二次是具体的转换。
使用std::conditional
和std::conditional_t
处理
#include
#include
struct T {};
struct F {};
int main() {
std::conditional_t<true, T, F> t;
std::conditional_t<false, T, F> f;
return 0;
}
使用std::enable_if
和std::enable_if_t
处理
#include
#include
int main() {
std::enable_if<true, T> t;
return 0;
}
使用模板的特化处理
#include
struct A;
struct B;
template<typename T>
struct Fun_ {
constexpr static int value = 0;
};
template<>
struct Fun_<A> {
constexpr static int value = 1;
};
template<>
struct Fun_<B> {
constexpr static int value = 2;
};
int main() {
std::cout << Fun_<int>::value << std::endl;
std::cout << Fun_<A>::value << std::endl;
std::cout << Fun_<B>::value << std::endl;
return 0;
}
/*
输出:
0
1
2
*/
使用if constexpr
处理
// C++17的表达式
if constexpr(/*...*/) {
// 如果括号是true,则运行这一段,否则运行下一段
} else {
}
简单实例,至少是C++17标准才可以
#include
#include
template<bool Tag>
auto fun() {
if constexpr (Tag) {
return static_cast<int>(1);
} else {
return std::string("hello world !\n");
}
}
int main() {
std::cout << fun<true>() << std::endl;
std::cout << fun<false>() << std::endl;
return 0;
}
输出:
1
hello world !
几个特殊点
不能在非完全特化的类模板中引入完全特化的分支代码!比如下面这一个错误的片段:
template<typename TF>
struct FOO {
template<typename T>
struct Fun_ {};
template<>
struct Fun_<int> {};
};
解决方式很简单,引入一个无用的默认void
模板即可:
template<typename TF>
struct FOO {
template<typename T, typename TDummy = void>
struct Fun_ {};
template<typename TDummy>
struct Fun_<int, TDummy> {};
};
C++11的SFINAE特性,特性的全称是Substitution Failed Is Not An Error,匹配失败不是错误。如果出现多个匹配的情况,编译器会寻找最佳匹配方案,不存在找不到匹配的情况,下面一个实例:
#include
#include
int fun(int a) {
return a;
}
std::string fun(const std::string& str) {
return str;
}
int main() {
std::cout << fun("foo") << std::endl;
std::cout << fun(1.1f) << std::endl;
return 0;
}
代码的fun
只匹配了std::string
和int
参数,有一个传入了float
参数,根据SFINAE
特性,这不是错误,而是主动匹配与之最接近的int
类型。
上述的std::enable_if
可以根据这个特性来构造:
#include
#include
template<bool CheckOut, typename T,
std::enable_if_t<CheckOut>* = nullptr>
auto CheckOut_ (T && arg) {
std::cout << "true, " << arg << std::endl;
}
template <bool CheckOut, typename T,
std::enable_if_t < !CheckOut > * = nullptr >
auto CheckOut_ (T&& arg) {
std::cout << "false, " << arg << std::endl;
}
int main() {
CheckOut_<true, int>(5);
CheckOut_<false, float>(1.1);
return 0;
}
短路操作一般是指的运行期的计算短路操作,对于编译期间的元函数操作影响不大。先给出一个运行期短路操作的例子:
#include
bool fun() {
std::cout << "fun()" << std::endl;
return false;
}
bool foo() {
std::cout << "foo()" << std::endl;
return true;
}
int main() {
int N;
std::cin >> N;
(N % 2 == 0) && fun() && foo();
return 0;
}
输入奇数,则没有任何输出;输入偶数,则执行fun()
函数;foo
永远不会被执行。
然而编译期间不存在上述的短路行为,即&&
不会阻止模板的展开。假设有下面的代码,其中meta1
和meta2
假设都是元函数:
meta1<10> && meta2<100>;
则不论meta1<10>
执行结果如何,meta2<100>
都会递归展开执行。
编译期间的短路操作需要依靠模板的特化完成。给出一个例子,模板元函数AllOdd_
需要判断M
和N
是否都是奇数,如果M
不是奇数,那么N
就不用判断了,直接短路操作即可。下面给出代码实例:
#include
#include
template <size_t N>
struct IsOdd_ {
constexpr static bool value = ((N % 2) == 1);
};
template <bool cur, typename TNext>
constexpr static bool AndValue = false;
template <typename TNext>
constexpr static bool AndValue<true, TNext> = TNext::value;
template <size_t M, size_t N>
struct AllOdd_ {
constexpr static bool is_M_odd = IsOdd_<M>::value;
// 利用AndValue特化实现短路操作
constexpr static bool value = AndValue<is_M_odd, IsOdd_<N>>;
};
int main() {
bool res1 = AllOdd_<2, 3>::value; // 输出0
bool res2 = AllOdd_<7, 9>::value; // 输出1
std::cout << res1 << std::endl;
std::cout << res2 << std::endl;
return 0;
}
模板元编程的循环本质上是模板在编译期间的递归展开。编译器会保留递归展开的结果,从而为复用做优化,这是好的方面;坏的方面是,不是所用的重复结果都会被充分地利用,这样可能会出现组合爆炸的情况,导致内存占用过多,造成空间浪费。
首先给出一个简单的实例,利用模板元求解1-N的累加和:
#include
// 泛型的模板
template <size_t N>
constexpr size_t Sum = N + Sum<N - 1>;
// 模板特化,相当于递归终止的条件
template<>
constexpr size_t Sum<1> = 1;
int main() {
size_t s = Sum<10>;
std::cout << s << std::endl; // 输出55
return 0;
}
上述代码中,没有出现循环结构,而是使用模板的递归展开进行计算,典型的函数式编程,不过是在编译期间进行计算的,这是模板元编写循环的典型使用方式。
再给出一个模板元容器的例子:
#include
// 模板特化,递归终止的条件是没有参数,没有模板就表示没有参数
template<size_t ...Inputs>
constexpr size_t Sum = 0;
// 泛型模板,函数式编程处理累加和
template <size_t CurInput, size_t ...Inputs>
constexpr size_t Sum<CurInput, Inputs...> = \
CurInput + Sum<Inputs...>;
int main() {
size_t s = Sum<1, 2, 3, 4, 5>;
std::cout << s << std::endl; // 输出15
return 0;
}
一般来说,编译器会保留编译的结果,留作复用,从而提高编译速度。但是,复用的前提是模板的参数必须完全一致才可以!!否则无法复用,而且随着循环展开的,可能出现组合爆炸的情况,导致内存不足。
上述第一个累加和例子中,如果有:
size_t s1 = Sum<5>;
size_t s2 = Sum<10>;
那么,对于Sum<10>
来说,1-5的累加和不用进行计算,而是直接使用Sum<5>
的计算结果。但是,如果出现下面这种参数不匹配的情况,则无法进行重复利用,反而会存在大量的重复性计算。
给出一个区间累加和的模板元函数,计算M-N的累加值,闭区间的。
#include
template <int M> // 起始值
struct Begin {
template<int N, typename TDummy = void>
struct Sum_ { // 泛型累加模板
constexpr static int value = N + Sum_ < N - 1 >::value;
};
template<typename TDummy>
struct Sum_<M, TDummy> { // 模板特化,终止值
constexpr static int value = M;
};
template<int N> // 输出的结果
constexpr static int End = Sum_<N>::value;
};
int main() {
int s = Begin < -10 >::End<6>;
int t = Begin < -11 >::End<6>;
std::cout << s << std::endl;
std::cout << t << std::endl;
return 0;
}
上述模板中,虽然-10+…+5的过程和值是相同,但是由于参数不同,导致模板无法利用,所以产生模板空间浪费。正确的做法是拆分结构。