一、元编程处理逻辑
无论在普通编程还是在元编程中,逻辑的处理,都是一个编程开始的必然经过。开发者对普通编程中的逻辑处理一般都非常清楚,不外乎条件谈判和循环处理。而条件判断常见的基本就是if语句(switch如果不考虑效率等情况,其实都类似)。而循环则有几类for,while等。
而开发者都知道,不管是在什么情况下编程,这种基本的逻辑处理,都是不可避免的。所以要想掌握好元编程,就必须掌握这些基本的逻辑处理,也就是在元编程中,如何进行条件处理和循环处理。
二、条件
在元编程中处理条件逻辑,一般有两大类,即传统的方法和C++新标准下的方法,主要有:
传统方法:
1、三目运算符
这个相对比较简单,看代码:
c
template<int N>
struct Comp
{
constexpr static int result = N > 3 ? 3 : N;
};
int main()
{
std::cerr << Comp<7>::result << std::endl;
}
2、模拟的特化和偏特化
c
//偏特化
template<class T, int I> // 主模板
struct A
{
void f(); // 成员声明
};
template<class T, int I>
void A<T, I>::f() {} // 主模板成员定义
// 部分特化
template<class T>
struct A<T, 2>
{
void f();
void g();
void h();
};
//全特化
#include <type_traits>
template<typename T> // 主模板
struct is_void : std::false_type {};
template<> // 对 T = void 的显式特化
struct is_void<void> : std::true_type {};
int main()
{
static_assert(is_void<char>::value == false,
"对于任何非 void 的类型 T,该类均派生自 false_type");
static_assert(is_void<void>::value == true,
"但当 T 是 void 时,类派生自 true_type");
}
3、SFINAE技术
比较典型的是std::enable_if,std::is_same等都能实现某种条件判断,如下面的例子:
c
template<class T>
typename std::enable_if<std::is_trivially_default_constructible<T>::value>::type
construct(T*)
{
std::cout << "默认构造可平凡默认构造的 T\n";
}
c++11标准及以后:
1、std::conditional
一般开发者在非模板编程中用到还是比较少的,但在模板开发中,还是比较常见的,看代码:
c
//c++11
#include <iostream>
#include <type_traits>
#include <typeinfo>
int main()
{
using Type1 = std::conditional<true, int, double>::type;
using Type2 = std::conditional<false, int, double>::type;
using Type3 = std::conditional<sizeof(int) >= sizeof(double), int, double>::type;
std::cout << typeid(Type1).name() << '\n';
std::cout << typeid(Type2).name() << '\n';
std::cout << typeid(Type3).name() << '\n';
}
//c++17
template<bool B>
using MyType = std::conditional_t<B, int, double>;
2、C++17的 if constexpr
c
template<typename T>
constexpr auto dowith(T t) {
if constexpr (std::is_pointer_v<T>) {
return *t;
} else if constexpr (std::is_array_v<T>) {
return sizeof(T);
} else {
return t;
}
}
constexpr int d = 30;
static_assert(dowith(2) == 2, "");
static_assert(dowith(&d) == 30, "");
static_assert(dowith("arr") == 5, "");//编译错误
3、Concepts
这个在前面反复分析过,只给一个简单的例子:
c
template<class T, class U>
concept Derived = std::is_base_of<U, T>::value;
不同的概念来限制不同的实例的生成,则可以实现条件逻辑的处理。
如果有对这些技术不太熟悉的,可翻看前面的相关文章或直接搜索相关资料,特别是SFINAE的资料,还是比较多的。不过对于未接触过模板编程或者经验较少的开发者来说,学习和应用起来还是有一些难度的。
三、循环
说了条件逻辑处理,就可以看元编程的循环逻辑处理了,在元编程中,循环相对来说要麻烦一些。主要可以分为以下几种:
1、递归模拟
这个最常见的就是斐波那切数列的元编程的例子,在前面也提到过,这里再看一下:
c
template<int N>
constexpr int factorial() {
return N * factorial<N-1>();
}
template<>
constexpr int factorial<0>() {
return 1;
}
static_assert(factorial<5>() == 120, "");
其展开的过程如下:
c
template<int N>
inline constexpr int factorial()
{
return N * factorial<N - 1>();
}
/* First instantiated from: insights.cpp:11 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
inline constexpr int factorial<5>()
{
return 5 * factorial<5 - 1>();
}
#endif
/* First instantiated from: insights.cpp:3 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
inline constexpr int factorial<4>()
{
return 4 * factorial<4 - 1>();
}
#endif
/* First instantiated from: insights.cpp:3 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
inline constexpr int factorial<3>()
{
return 3 * factorial<3 - 1>();
}
#endif
/* First instantiated from: insights.cpp:3 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
inline constexpr int factorial<2>()
{
return 2 * factorial<2 - 1>();
}
#endif
/* First instantiated from: insights.cpp:3 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
inline constexpr int factorial<1>()
{
return 1 * factorial<1 - 1>();
}
#endif
template<>
inline constexpr int factorial<0>()
{
return 1;
}
/* PASSED: static_assert(factorial<5>() == 120, ""); */
这里就可以认为递归模拟了循环展开。
2、使用折叠表达式
这种方法应用于一些比较特殊的场景,看代码:
c
#include <iostream>
#include <tuple>
template<typename... Args>
void traverse(const std::tuple<Args...>& t) {
auto print = [](const auto&... args) {
(..., (std::cout << args << " "));
};
std::apply(print, t);
}
int main() {
std::tuple<int, double, std::string> t(1, 2.3, "test");
traverse(t);
return 0;
}
3、使用一些模板的技巧实现循环,包括标准迭代的版本
c
//基本
#include <iostream>
#include <type_traits>
template<typename... Ts>
struct TypeContainer {};
template<typename Candidate, typename Container>
struct is_contained
{
constexpr static bool value = false;
};
template<typename Candidate>
struct is_contained<Candidate, TypeContainer<>>
{
constexpr static bool value = false;
};
template<typename Candidate, typename Cur, typename... Ts>
struct is_contained<Candidate, TypeContainer<Cur, Ts...>>
{
constexpr static bool value =
std::is_same_v<Candidate, Cur> ||
is_contained<Candidate, TypeContainer<Ts...>>::value;
};
int main()
{
std::cout << is_contained<char, TypeContainer<float, int, double>>::value << std::endl;
std::cout << is_contained<double, TypeContainer<float, int, double>>::value << std::endl;
std::cout << is_contained<int, TypeContainer<>>::value << std::endl;
}
//c++17
template<typename Candidate, typename... Ts>
struct is_contained;
template<typename Candidate, typename... Ts>
struct is_contained<Candidate, TypeContainer<Ts...>>
{
constexpr static bool value = []() {
if constexpr (sizeof...(Ts) == 0) {
return false;
} else {
return (std::is_same_v<Candidate, Ts> || ...);
}
}();
};
//c++20
template<typename Candidate, typename... Ts>
constexpr bool is_contained_v = (std::is_same_v<Candidate, Ts> || ...);
// 使用示例
static_assert(is_contained_v<double, float, int, double>);
static_assert(!is_contained_v<char, float, int, double>);
4、使用const方式使用传统的for展开
看下面的代码:
c
#include <iostream>
#include <array>
template<typename T, size_t N>
constexpr void traverse(const std::array<T, N>& arr) {
for (size_t i = 0; i < N; ++i) {
std::cout << arr[i] << " ";
}
}
int main() {
constexpr std::array<int, 4> arr{2, 5, 3, 7};
traverse(arr);
return 0;
}
5、使用std::index_sequence
这个就不举例子了,前面有不少。
四、总结
在前面的模板和元编程中,其实对这些技术都有过仔细的分析,只不没有专门的组织起来,形成一个系统的说明。本文主要是让开发者从学习编程语言的习惯来学习一下元编程中的相关技术。
在这个AI有着泛滥的趋势的大形势下,要耐心,切不可浮躁!