跟我学c++高级篇——模板元编程之十三处理逻辑

一、元编程处理逻辑

无论在普通编程还是在元编程中,逻辑的处理,都是一个编程开始的必然经过。开发者对普通编程中的逻辑处理一般都非常清楚,不外乎条件谈判和循环处理。而条件判断常见的基本就是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有着泛滥的趋势的大形势下,要耐心,切不可浮躁!

相关推荐
虾球xz4 小时前
游戏引擎学习第268天:合并调试链表与分组
c++·学习·链表·游戏引擎
格林威5 小时前
Baumer工业相机堡盟工业相机的工业视觉中为什么偏爱“黑白相机”
开发语言·c++·人工智能·数码相机·计算机视觉
Dream it possible!6 小时前
LeetCode 热题 100_只出现一次的数字(96_136_简单_C++)(哈希表;哈希集合;排序+遍历;位运算)
c++·leetcode·位运算·哈希表·哈希集合
Dddle17 小时前
C++:this指针
java·c语言·开发语言·c++
不見星空8 小时前
2025年第十六届蓝桥杯软件赛省赛C/C++大学A组个人解题
c语言·c++·蓝桥杯
jiunian_cn8 小时前
【c++】异常详解
java·开发语言·数据结构·c++·算法·visual studio
梁下轻语的秋缘8 小时前
每日c/c++题 备战蓝桥杯(洛谷P1387 最大正方形)
c语言·c++·蓝桥杯
熬夜学编程的小王8 小时前
【C++进阶篇】多态
c++·多态·静态绑定与动态绑定
UpUpUp……9 小时前
Linux--JsonCpp
linux·运维·服务器·c++·笔记·json