CppCon 2020 学习:Template Metaprogramming:Type Traits
传统函数通过语句返回值,编译器会强制检查返回值类型和语法。但是元函数不是传统意义上的“函数”,而是一种用模板实现的特殊类或结构体。元函数的特点元函数不是语言内建的功能,C++标准没有对它们提供专门的语言支持。它们是利用已有的语言特性,通过约定俗成的编程习惯实现的。也就是说,元函数的实现和使用不是由语言强制执行的,而是社区和库中形成的“惯例”。这些惯例是C++社区为了方便元编程而共同遵守的标准写法。
元函数(Metafunctions)
传统函数 vs 元函数
- 传统函数就是我们平时写的函数:
- 可能有0个或多个参数
- 有返回值(或者void无返回)
- 例如:
void do_something(); int do_something_else(int, const char*); - 传统函数通过
return语句返回值,编译器会强制检查返回值类型和语法。
- 但是元函数不是传统意义上的“函数”,而是一种用模板实现的特殊类或结构体。
元函数的特点
- 元函数不是语言内建的功能,C++标准没有对它们提供专门的语言支持。
- 它们是利用已有的语言特性,通过约定俗成的编程习惯实现的。
- 也就是说,元函数的实现和使用不是由语言强制执行的,而是社区和库中形成的“惯例”。
- 这些惯例是C++社区为了方便元编程而共同遵守的标准写法。
元函数的形式
- 本质上是一个模板类(class template):
- 有0个或多个模板参数。
- 有0个或多个“返回值”,通常表现为这个类内部定义的某个成员(如
type、value)。
- 约定俗成的用法是:元函数应当只返回一个结果,类似普通函数返回一个值。
- 由于历史原因,存在不少不遵循这一规范的元函数实现,但现代元函数大多遵守这个“只返回一个东西”的惯例。
总结
- 传统函数:运行时,带参数和返回值。
- 元函数:编译时,用模板类模拟函数行为,通过模板参数和内部成员返回“值”。
- 元函数不是C++语言内置特性,而是“习惯用法”。
- C++社区已经形成一套元函数的标准约定。
- 现代元函数通常返回单一类型或值,方便使用。
你这部分内容讲的是元函数如何返回值,具体用什么机制来模拟“返回值”的概念。以下是理解:
元函数的返回值
元函数虽然不是传统的函数,但它们也需要“返回一个结果”,这个结果一般有两种常见的形式:
1. 通过公开的静态常量 value 返回值(常用于整型或常量)
示例:
template <typename T>
struct TheAnswer {
static constexpr int value = 42;
};
int main() {
//
static_assert(TheAnswer<int>::value == 42, "");
}
- 这里,
TheAnswer<T>是一个模板类(元函数)。 - 通过静态成员
value来“返回”整数 42。 - 使用时,可以写
TheAnswer<int>::value来访问结果。
2. 通过公开的类型成员 type 返回类型(常用于类型变换)
示例:
#include <type_traits>
template <typename T>
struct Echo {
using type = T;
};
int main() {
//
static_assert(std::is_same_v<Echo<int>::type, int>, "");
}
- 这里,
Echo<T>是一个模板类。 - 它“返回”的结果是类型
T,通过成员类型别名type暴露出来。 - 使用时,可以写
Echo<int>::type得到int类型。
总结
- 元函数“返回值”通常用静态成员来模拟:
static constexpr成员变量value用来返回值(比如常量、整数)。- 成员类型别名
type用来返回类型。
- 这样设计的元函数方便在编译期通过模板元编程进行计算和类型转换。
这部分讲的是值元函数(Value Metafunctions),也就是用模板和静态常量来实现“值传递”的元函数概念,和普通函数的“传值返回”做对比。下面是详细理解:
值元函数(Value Metafunctions)
1. 普通函数的例子:恒等函数(identity)
#include <cassert>
int int_identity(int x) { return x; }
int main() {
//
assert(42 == int_identity(42));
}
- 这是普通的运行时函数,输入一个整数,返回同样的整数。
2. 简单的值元函数:IntIdentity
template <int X>
struct IntIdentity {
static constexpr int value = X;
};
int main() {
//
static_assert(42 == IntIdentity<42>::value);
}
- 这里,
IntIdentity是一个模板结构体,模板参数是一个整数值X。 - 它“返回”的值是静态常量成员
value,值就是X。 - 这相当于在编译期实现的恒等函数,结果是一个编译期常量。
- 用法:
IntIdentity<42>::value编译期等于 42。
3. 泛型恒等函数(运行时)
#include <cassert>
template <typename T>
T identity(T x) {
return x;
}
int main() {
assert(42 == identity(42)); // 返回 int
assert(42ull == identity(42ull)); // 返回 unsigned long long
}
- 泛型函数模板,输入返回同样类型的值。
- 这是运行时的恒等函数。
4. 泛型值元函数(编译期)
template <typename T, T Value>
struct ValueIdentity {
static constexpr T value = Value;
};
int main() {
static_assert(42 == ValueIdentity<int, 42>::value);
static_assert(42ull == ValueIdentity<unsigned long long, 42ull>::value);
}
- 模板参数有两个:类型
T和值Value(值必须是类型T的常量)。 - 静态常量成员
value存储并暴露这个值。 - 这样可以在编译期获得不同类型的常量。
5. C++17 的自动模板参数简化写法
template <auto X>
struct ValueIdentity {
static constexpr auto value = X;
};
int main() {
static_assert(42 == ValueIdentity<42>::value);
static_assert(42ull == ValueIdentity<42ull>::value);
}
- 利用 C++17 的
auto非类型模板参数,自动推断模板参数类型。 - 更简洁,适合多种类型的值元函数。
总结
- 值元函数 是用模板参数和静态成员变量模拟的“函数”,用于在编译期传递和操作常量值。
- 它们和运行时函数的恒等函数类似,只不过发生在编译期。
- C++17 的
auto模板参数极大简化了值元函数的写法。
这部分内容讲的是“值元函数”的一个具体例子:计算两个值的和(Sum),并用编译时模板结构体模拟它,同时也给出了运行时普通函数的写法。以下是详细理解:
值元函数:求和(Sum)
1. 普通函数版本的求和
int sum(int x, int y) {
return x + y;
}
- 普通的运行时函数,接受两个整数参数,返回它们的和。
2. 编译时求和的元函数结构体
template <int X, int Y>
struct IntSum {
static constexpr int value = X + Y;
};
int main() {
//
static_assert(42 == IntSum<30, 12>::value);
}
- 这是一个模板结构体
IntSum,它有两个整数模板参数X和Y。 - 通过
static constexpr int value = X + Y;,在编译期计算两个值的和。 - 使用
static_assert断言在编译期验证结果正确。
3. 泛型运行时函数版本的求和
#include <cassert>
template <typename X, typename Y>
auto sum(X x, Y y) {
return x + y;
}
int main() {
//
assert(42ull == sum(30, 12ull)); // 返回 unsigned long long 类型
}
- 这是一个模板函数,接受任意类型的两个参数,返回它们的和。
- 返回类型由
auto自动推断,支持不同类型混合(比如int和unsigned long long)。 - 这里示例了
30和12ull(无符号长整型)的求和。
4. C++17 及以上版本的模板元函数求和
template <auto X, auto Y>
struct Sum {
static constexpr auto value = X + Y;
};
int main() {
//
static_assert(42ull == Sum<30, 12ull>::value);
}
- 这个版本使用了
auto非类型模板参数,允许传入任意类型的常量值(不限于int)。 value是静态常量表达式,计算两个模板参数的和。- 编译期断言验证结果。
5. 也可以用 constexpr 函数实现编译时求和:
template <typename X, typename Y>
constexpr auto sum(X x, Y y) {
return x + y;
}
int main() {
//
static_assert(42ull == sum(30, 12ull));
}
- 这是一个
constexpr函数,可以在编译期计算值。 - 使用
static_assert验证编译时结果。
总结
- 值元函数
Sum模拟了普通求和函数的行为,但它发生在编译期。 - 通过模板非类型参数和静态常量成员,编译期获得常量值。
- 结合
constexpr函数,也可以实现灵活的编译期计算。 - C++17 的
auto非类型模板参数让元函数更加通用,支持多类型求和。
这部分内容讲的是类型元函数(Type Metafunctions),重点是它们如何“返回”一个类型,以及它们在编译时编程中的重要作用。下面是详细理解:
类型元函数(Type Metafunctions)
1. 类型元函数的作用
- 类型元函数是元编程中的核心工具(特别是随着
constexpr的引入,变得更加强大)。 - 它们不是返回值,而是“返回”一个类型。
- 通过模板结构体的方式,实现编译时的类型映射和计算。
2. 典型的类型元函数示例:TypeIdentity
template <typename T>
struct TypeIdentity {
using type = T;
};
- 这个模板结构体接收一个类型参数
T。 - 它定义了一个别名
type,它就是传入的类型T。 - 可以理解为:这个元函数“返回”的类型就是传入的类型本身。
- 这个例子是最简单的类型元函数,主要用于“包装”一个类型。
3. C++20 标准新增 std::type_identity
- 标准库新增了
std::type_identity,功能与上述自定义的TypeIdentity一样。 - 作用是提供一种标准化的方式来实现“类型包装”和传递,方便类型元编程。
- 它定义在
<type_traits>头文件中。
总结
- 类型元函数通过模板定义类型别名来“返回”类型。
- 它们是模板元编程中处理类型变换、推导的基础工具。
TypeIdentity是最简单的类型元函数示例,返回传入类型。- C++20 引入了标准的
std::type_identity来统一这种用法。
这部分内容讲的是如何调用类型元函数(Type Metafunctions)和数值元函数(Value Metafunctions),以及便捷调用的约定。下面是详细的理解:
调用类型元函数和数值元函数
1. 直接调用示例
- 数值元函数的调用:
例如ValueIdentity<42>::value,访问模板结构体中的静态常量成员value。
这就是“调用”数值元函数,得到编译时常量值。 - 类型元函数的调用:
例如TypeIdentity<int>::type,访问类型别名type,得到返回的类型(这里是int)。
注意访问类型成员时需要用typename关键字(后面会讲“typename舞蹈”)。 - “typename 舞蹈”:
当访问依赖模板参数的嵌套类型成员时,必须写成:
这里的typename TypeIdentity<T>::typetypename告诉编译器type是一个类型名。
2. 便捷调用约定(Convenience Calling Conventions)
为了简化调用,C++ 社区形成了两种命名约定:
数值元函数
- 使用变量模板(variable template),名字以
_v结尾。 - 例如:
template <auto X> inline constexpr auto ValueIdentity_v = ValueIdentity<X>::value; - 这样调用时就可以直接写:
代替static_assert(42 == ValueIdentity_v<42>);ValueIdentity<42>::value,写法更简洁。
类型元函数
- 使用别名模板(alias template),名字以
_t结尾。 - 例如:
template <typename T> using TypeIdentity_t = typename TypeIdentity<T>::type; - 调用时写成:
代替static_assert(std::is_same_v<int, TypeIdentity_t<int>>);typename TypeIdentity<int>::type,写法更简洁。
3. 总结
- 直接调用类型元函数和数值元函数需要访问其成员
type或value,且类型成员访问需要typename。 - 为了简化使用,社区约定提供变量模板和别名模板(分别以
_v和_t结尾)作为便捷方式。 - 这些便捷写法需要手动定义。
#include <type_traits>
#include <iostream>
// 数值元函数:返回编译时常量value
template <auto X>
struct ValueIdentity {
static constexpr auto value = X;
};
// 变量模板,方便调用数值元函数
template <auto X>
inline constexpr auto ValueIdentity_v = ValueIdentity<X>::value;
// 类型元函数:返回类型type
template <typename T>
struct TypeIdentity {
using type = T;
};
// 别名模板,方便调用类型元函数
template <typename T>
using TypeIdentity_t = typename TypeIdentity<T>::type;
int main() {
// 直接调用数值元函数
static_assert(ValueIdentity<42>::value == 42);
std::cout << "ValueIdentity<42>::value = " << ValueIdentity<42>::value << '\n';
// 使用变量模板调用数值元函数,更简洁
static_assert(ValueIdentity_v<100> == 100);
std::cout << "ValueIdentity_v<100> = " << ValueIdentity_v<100> << '\n';
// 直接调用类型元函数,需要typename
static_assert(std::is_same_v<int, typename TypeIdentity<int>::type>);
std::cout << "TypeIdentity<int>::type is int\n";
// 使用别名模板调用类型元函数,更简洁
static_assert(std::is_same_v<float, TypeIdentity_t<float>>);
std::cout << "TypeIdentity_t<float> is float\n";
return 0;
}
std::integral_constant 是一个非常有用的类型元函数(metafunction),它用来在编译期封装一个常量值,方便做类型级别的计算和传递。它是 C++ 类型元编程中基础且常用的工具。
理解说明:
template <class T, T v>
struct integral_constant {
static constexpr T value = v; // 静态常量成员,保存传入的值 v
using value_type = T; // 该常量的类型
using type = integral_constant<T, v>; // 类型别名,指向自身类型
// constexpr 转换操作符,允许 integral_constant 对象隐式转换为其值
constexpr operator value_type() const noexcept {
return value;
}
// 函数调用操作符,返回存储的值
constexpr value_type operator()() const noexcept {
return value;
}
};
关键点解析:
T是常量的类型,比如int,bool等。v是要封装的值,比如42,true等。value是一个constexpr静态成员变量,编译期可用。value_type是常量的类型,方便引用。type是对自身类型的别名,有时在元编程中用来递归传递。operator value_type()允许你把这个类型的实例当作值来用,比如隐式转换成int。operator()()让你用函数调用的形式获得值。
用途举例:
std::integral_constant 在标准库中被广泛使用,比如:
std::true_type和std::false_type分别是integral_constant<bool, true>和integral_constant<bool, false>,用来表示编译期的布尔值。- 结合
std::conditional,std::enable_if等进行条件编译和类型选择。 - 作为类型特征(type traits)的一部分返回结果。
总结:
std::integral_constant 是把一个值和类型绑在一起的简单工具,方便在编译期进行类型计算和条件判断,是 C++ 模板元编程的基础构件之一。
下面给出一个关于 std::integral_constant 的完整例子,演示它的基本用法和一些常见场景:
#include <iostream>
#include <type_traits>
// 1. 定义一个简单的 integral_constant 实例
using my_const = std::integral_constant<int, 42>;
int main() {
// 2. 访问 value
std::cout << "my_const::value = " << my_const::value << "\n";
// 3. 创建对象,隐式转换为 int
my_const c;
int val = c; // 隐式调用 operator value_type()
std::cout << "Implicit conversion: val = " << val << "\n";
// 4. 使用函数调用操作符获取值
std::cout << "Function call operator: c() = " << c() << "\n";
// 5. 使用 true_type 和 false_type 进行条件编译示例
if constexpr (std::is_same_v<my_const, std::integral_constant<int, 42>>) {
std::cout << "my_const is integral_constant<int, 42>\n";
}
// 6. 结合 std::conditional,基于编译时常量选择类型
using SelectedType = std::conditional_t<my_const::value == 42, int, double>;
SelectedType x = 100;
std::cout << "SelectedType is " << typeid(SelectedType).name() << ", value = " << x << "\n";
return 0;
}
代码说明:
my_const是integral_constant<int, 42>的别名,封装了整数 42。- 通过
my_const::value访问编译期常量。 integral_constant可以隐式转换成它封装的值类型。- 也可以用函数调用操作符
()访问值。 true_type和false_type是integral_constant<bool, true>和integral_constant<bool, false>的特化,用于编译期布尔值判断。std::conditional_t根据my_const::value在编译期选择类型。
运行示例输出:
my_const::value = 42
Implicit conversion: val = 42
Function call operator: c() = 42
my_const is integral_constant<int, 42>
SelectedType is i, value = 100
i是int类型的编译器内部名称。
下面是对 std::bool_constant 及其相关内容的理解:
std::bool_constant
std::bool_constant是一个模板别名,定义为:
template <bool B>
using bool_constant = std::integral_constant<bool, B>;
- 它是对
std::integral_constant<bool, B>的简单封装,专门用于布尔值。
方便的别名
std::true_type和std::false_type是bool_constant的两个特化:
using true_type = bool_constant<true>;
using false_type = bool_constant<false>;
- 它们分别代表常量
true和false。
它们的用途
true_type和false_type是无参数的(nullary)元函数(metafunction),因为它们没有模板参数。- 它们都拥有静态成员常量
value,分别是true和false。
例如:
static_assert(std::true_type::value == true);
static_assert(std::false_type::value == false);
总结
std::bool_constant用来方便地定义编译期布尔常量类型。true_type和false_type是常用的预定义布尔类型,广泛用于模板元编程中进行条件判断和分支选择。
下面是一个完整的示例代码,演示了 std::integral_constant、std::bool_constant 以及 true_type 和 false_type 的用法和作用:
#include <iostream>
#include <type_traits>
// 自定义打印类型信息辅助函数
template <typename T>
void print_bool_constant() {
std::cout << "value = " << T::value << "\n";
std::cout << "operator bool() = " << static_cast<bool>(T{}) << "\n";
}
int main() {
// std::integral_constant 示例
using five_t = std::integral_constant<int, 5>;
std::cout << "five_t::value = " << five_t::value << "\n";
std::cout << "five_t() = " << five_t() << "\n"; // operator() 调用
// std::bool_constant 示例
using true_const = std::bool_constant<true>;
using false_const = std::bool_constant<false>;
std::cout << "\ntrue_const:\n";
print_bool_constant<true_const>();
std::cout << "\nfalse_const:\n";
print_bool_constant<false_const>();
// 预定义的 true_type 和 false_type
std::cout << "\nstd::true_type::value = " << std::true_type::value << "\n";
std::cout << "std::false_type::value = " << std::false_type::value << "\n";
// 结合 if constexpr 用法演示
if constexpr (std::true_type::value) {
std::cout << "条件成立:std::true_type::value 为 true\n";
} else {
std::cout << "条件不成立\n";
}
return 0;
}
代码说明:
std::integral_constant<int, 5>定义了一个类型five_t,其静态常量value为 5。std::bool_constant<true>和std::bool_constant<false>分别表示布尔值true和false。std::true_type和std::false_type是这两个布尔常量的预定义别名。print_bool_constant模板函数演示了访问.value和隐式转换到bool的行为。- 使用
if constexpr演示了在编译期根据类型布尔值做条件分支。
给的内容是关于 C++17 标准中 类型特征(type traits) 的分类和设计规范的总结,我帮你用做详细理解说明:
Cpp17UnaryTypeTrait(一元类型特征)
- 是一个类模板,只接受一个模板类型参数。
- 该类模板满足以下条件:
- 支持 C++17 标准的默认构造(DefaultConstructible)。
- 支持 C++17 标准的拷贝构造(CopyConstructible)。
- 继承自
std::integral_constant的某个特化(specialization)。 - 继承的基类中的成员名不会被隐藏,且可以明确访问(unambiguously available)。
这类一元类型特征通常用来检测一个类型是否满足某种条件,结果以true_type或false_type(都继承自integral_constant<bool, ...>)的形式返回。
Cpp17BinaryTypeTrait(二元类型特征)
- 是一个类模板,接受两个模板类型参数。
- 也满足默认构造、拷贝构造的要求。
- 同样继承自
std::integral_constant的某个特化。 - 基类成员名不隐藏且可明确访问。
二元类型特征通常用于判断两个类型之间的关系,比如std::is_same<T, U>判断两个类型是否相同。
Cpp17TransformationTrait(类型转换特征)
- 也是一个类模板,接受一个模板类型参数。
- 其核心是定义了一个公开嵌套类型
type,表示经过转换后的类型。 - 对这个类模板不要求默认构造和拷贝构造能力。
- 不要求继承自
integral_constant。
类型转换特征是用来对类型做转换,比如std::remove_const<T>移除类型T的const限定符,结果通过type嵌套类型来表达。
总结
| 特征类型 | 模板参数个数 | 继承 integral_constant |
默认/拷贝构造要求 | 有无嵌套类型 type |
主要用途 |
|---|---|---|---|---|---|
| 一元类型特征(Unary) | 1 | 是 | 是 | 否 | 判断某类型属性(bool) |
| 二元类型特征(Binary) | 2 | 是 | 是 | 否 | 判断两个类型关系(bool) |
| 类型转换特征(Transform) | 1 | 否 | 否 | 是 | 类型变换,返回新类型 |
| 这就是 C++17 标准中针对类型特征的分类设计要求。每种类型特征在标准库里的对应实现,都遵循这些规范。 |
提供的内容是关于 C++ 标准库类型特征使用时 未定义行为(Undefined Behavior) 的说明,下面帮你用详细解读:
未定义行为(Undefined Behavior)
1. 不要对标准类型特征进行特化
- 标准库中已经定义好的类型特征(比如
std::is_same、std::is_integral等)不允许用户自己去特化(specialize)。 - 否则会导致未定义行为,这意味着程序的行为无法预测,可能出现编译错误、运行时崩溃或其他异常。
2. 使用不完全类型时要非常小心
- 不完全类型(Incomplete Type) 指的是只声明了类型但没有完整定义的类型,比如:
class Foo; // 这是声明,Foo是一个不完全类型 - 标准允许用不完全类型来实例化模板(即模板参数可以是不完全类型),除非出现以下情况,程序行为就是未定义的:
- (5.1) 模板实例化直接或间接依赖于某个不完全类型
T。 - (5.2) 该实例化的结果在如果
T完整定义了之后可能会有不同。
换句话说,如果模板实例化依赖的类型还没完全定义,而这个实例化的结果本质上会因为类型的完整定义而改变,这样的代码行为就是未定义。
- (5.1) 模板实例化直接或间接依赖于某个不完全类型
总结
- 不允许用户特化标准类型特征,否则程序会变得不可预测。
- 对于模板参数是某个类的指针或引用时,可以暂时使用不完全类型,但如果模板实例化依赖于类型细节,必须确保类型是完整的。
- 使用不完全类型时,必须保证模板的结果不会随着类型定义的补全而改变,否则会出现未定义行为。
你这段内容讲的是 C++ 标准库中的类型特征 std::is_void,它是一个一元类型特征(Unary Type Trait),用来判断一个类型是否是 void(或者带 const、volatile 修饰的 void)。
我帮你逐步用解读总结:
std::is_void — 判断类型是否为 void
1. 类型特征定义
is_void 是一个值特征(value metafunction),会判断给定类型 T 是否是 void 类型。
它的结果是一个类型,要么是 std::true_type,表示是 void,要么是 std::false_type,表示不是 void。
2. 实现机制(特化)
- 主模板(primary template):对所有类型,默认认为不是
void,继承自std::false_type。
template <typename T>
struct is_void : std::false_type {};
- 特化版本:针对
void以及带有 cv 限定符(const、volatile)的void类型,特化为继承std::true_type。
template <>
struct is_void<void> : std::true_type {};
template <>
struct is_void<void const> : std::true_type {};
template <>
struct is_void<void volatile> : std::true_type {};
template <>
struct is_void<void const volatile> : std::true_type {};
3. cv 限定符的处理
is_void 对于 void 的 const、volatile 和 const volatile 版本都会被认为是 void,所以都会返回 true_type。
标准规定:对于任意类型
T,is_void<T>和is_void<cv T>的结果应该是一样的。
4. 变量模板简化使用
C++17 之后,is_void 也提供了变量模板,方便直接拿到布尔值:
template <typename T>
inline constexpr bool is_void_v = is_void<T>::value;
使用时就可以写:
static_assert(is_void_v<void>); // true
static_assert(!is_void_v<int>); // false
static_assert(is_void_v<void const>); // true
总结
is_void用来判断类型是不是void类型(包括带const/volatile修饰的)。- 默认模板返回
false,对void及其 cv 限定的版本进行特化返回true。 - 变量模板
is_void_v<T>简化调用。 - 这符合 C++ 标准的类型分类规则。
这段内容讲的是 C++ 标准库中的类型特征 std::is_void,它是一个一元类型特征(Unary Type Trait),用来判断一个类型是否是 void(或者带 const、volatile 修饰的 void)。
我帮你逐步用解读总结:
std::is_void — 判断类型是否为 void
1. 类型特征定义
is_void 是一个值特征(value metafunction),会判断给定类型 T 是否是 void 类型。
它的结果是一个类型,要么是 std::true_type,表示是 void,要么是 std::false_type,表示不是 void。
2. 实现机制(特化)
- 主模板(primary template):对所有类型,默认认为不是
void,继承自std::false_type。
template <typename T>
struct is_void : std::false_type {};
- 特化版本:针对
void以及带有 cv 限定符(const、volatile)的void类型,特化为继承std::true_type。
template <>
struct is_void<void> : std::true_type {};
template <>
struct is_void<void const> : std::true_type {};
template <>
struct is_void<void volatile> : std::true_type {};
template <>
struct is_void<void const volatile> : std::true_type {};
3. cv 限定符的处理
is_void 对于 void 的 const、volatile 和 const volatile 版本都会被认为是 void,所以都会返回 true_type。
标准规定:对于任意类型
T,is_void<T>和is_void<cv T>的结果应该是一样的。
4. 变量模板简化使用
C++17 之后,is_void 也提供了变量模板,方便直接拿到布尔值:
template <typename T>
inline constexpr bool is_void_v = is_void<T>::value;
使用时就可以写:
static_assert(is_void_v<void>); // true
static_assert(!is_void_v<int>); // false
static_assert(is_void_v<void const>); // true
总结
is_void用来判断类型是不是void类型(包括带const/volatile修饰的)。- 默认模板返回
false,对void及其 cv 限定的版本进行特化返回true。 - 变量模板
is_void_v<T>简化调用。 - 这符合 C++ 标准的类型分类规则。
下面是一个完整的示例代码,演示 std::is_void 类型特征的实现原理和用法,包括对 void 及其 const、volatile 修饰版本的特化,同时展示变量模板 is_void_v 的使用。
#include <iostream>
#include <type_traits>
// 主模板:默认不是 void
template <typename T>
struct is_void : std::false_type {};
// 针对 void 及其 cv 限定的特化,返回 true
template <>
struct is_void<void> : std::true_type {};
template <>
struct is_void<void const> : std::true_type {};
template <>
struct is_void<void volatile> : std::true_type {};
template <>
struct is_void<void const volatile> : std::true_type {};
// 变量模板,方便直接获取 bool 值(C++17起支持)
template <typename T>
inline constexpr bool is_void_v = is_void<T>::value;
// 测试函数,打印类型是否是 void
template <typename T>
void test_is_void() {
std::cout << "is_void_v<" << typeid(T).name() << "> = "
<< (is_void_v<T> ? "true" : "false") << "\n";
}
int main() {
test_is_void<void>(); // true
test_is_void<void const>(); // true
test_is_void<void volatile>(); // true
test_is_void<void const volatile>(); // true
test_is_void<int>(); // false
test_is_void<int*>(); // false
test_is_void<double>(); // false
return 0;
}
输出示例:
is_void_v:void = true
is_void_v:void const = true
is_void_v:void volatile = true
is_void_v:void const volatile = true
is_void_v:int = false
is_void_v:int* = false
is_void_v:double = false
说明
is_void是一个模板结构体,默认继承自std::false_type(表示不是 void)。- 对
void及其const、volatile和const volatile版本做了特化,继承自std::true_type。 - 变量模板
is_void_v<T>是is_void<T>::value的简写,用起来更方便。 - 通过
test_is_void函数可以直观地看到各种类型是否被判断为void。
下面是对 remove_const 变换特征的完整理解及示例代码的讲解:
remove_const (变换特征)
正式定义
remove_const<T> 是一个模板结构体,它的成员类型 type 表示去掉了类型 T 顶层的 const 限定后的类型。
具体行为
| 输入类型 | remove_const<T>::type |
|---|---|
int |
int |
const int |
int |
const volatile int |
volatile int |
int* |
int* |
const int* |
const int* (顶层指针不移除) |
int const * const |
int const* |
int * const |
int* |
| 说明: | |
只会移除顶层的 const。例如,const int* 是指向常量的指针,顶层 const 是指针本身是否 const,这里的顶层 const 指的是最外层的 const。如果指针本身带 const,会被移除,但指针指向的内容的 const 不会被移除。 |
代码示例
#include <type_traits>
#include <iostream>
// 简单的类型别名工具
template <typename T>
struct TypeIdentity {
using type = T;
};
// 主模板:不带 const 时,type 保持原样
template <typename T>
struct remove_const : TypeIdentity<T> {};
// 偏特化:当 T 是 const 修饰时,去掉顶层 const
template <typename T>
struct remove_const<T const> : TypeIdentity<T> {};
// 方便使用的别名模板(C++11 及以上)
template <typename T>
using remove_const_t = typename remove_const<T>::type;
// 测试函数,用于打印类型是否相同
template <typename T, typename U>
void test() {
std::cout << std::boolalpha
<< std::is_same<T, U>::value << "\n";
}
int main() {
// 基础类型
test<remove_const_t<int>, int>(); // true
test<remove_const_t<const int>, int>(); // true
test<remove_const_t<const volatile int>, volatile int>(); // true
// 指针类型
test<remove_const_t<int*>, int*>(); // true
test<remove_const_t<const int*>, const int*>(); // true,指针顶层无 const,不变
test<remove_const_t<int* const>, int*>(); // true,顶层 const 被移除
// 复杂例子
test<remove_const_t<int const * const>, int const*>(); // true
return 0;
}
解释
- 主模板处理非
const类型,直接返回原类型。 - 偏特化匹配
T const,去掉顶层const,将T const转换为T。 - 变量模板
remove_const_t<T>是方便用户直接获取结果类型。
总结
remove_const用于移除类型顶层的const限定。- 不影响指针指向的类型上的
const,只去除最外层的。 - C++ 标准库中提供了对应的
std::remove_const和std::remove_const_t,行为相同。
下面是对 conditional 模板及示例的理解和完整解释:
conditional (条件型变换特征)
conditional 是一个根据布尔条件选择类型的模板结构体。类似于普通的三元表达式 Condition ? T : F,但在编译期用于类型选择。
定义
// 当 Condition 为 true 时,conditional 继承自 TypeIdentity<T>,即结果类型为 T
template <bool Condition, typename T, typename F>
struct conditional : TypeIdentity<T> { };
// 当 Condition 为 false 时,partial specialization 继承自 TypeIdentity<F>,结果类型为 F
template <typename T, typename F>
struct conditional<false, T, F> : TypeIdentity<F> { };
// 方便使用的别名模板
template <bool Condition, typename T, typename F>
using conditional_t = typename conditional<Condition, T, F>::type;
使用示例
static_assert(std::is_same_v<int,
conditional_t<is_void<void>::value, int, long>>);
static_assert(std::is_same_v<long,
conditional_t<is_void<char>::value, int, long>>);
- 当
is_void<void>::value为true,conditional_t<true, int, long>结果是int。 - 当
is_void<char>::value为false,conditional_t<false, int, long>结果是long。
解释总结:
conditional是一个编译期的条件类型选择器。- 第一个模板参数是布尔值
Condition。 - 如果
Condition == true,conditional继承自TypeIdentity<T>,类型为T。 - 如果
Condition == false,conditional偏特化继承自TypeIdentity<F>,类型为F。 conditional_t是获取最终类型的便捷别名。- 常用于根据编译时条件选择不同类型。
下面是完整的 conditional 及相关辅助代码的例子,包括 TypeIdentity 和 is_void,方便你理解它们是如何协作的:
#include <type_traits>
#include <iostream>
// TypeIdentity,简单封装类型
template <typename T>
struct TypeIdentity {
using type = T;
};
// is_void 类型特征,用于判断是否是 void 类型
template <typename T>
struct is_void : std::false_type {};
template <>
struct is_void<void> : std::true_type {};
template <>
struct is_void<void const> : std::true_type {};
template <>
struct is_void<void volatile> : std::true_type {};
template <>
struct is_void<void const volatile> : std::true_type {};
template <typename T>
inline constexpr bool is_void_v = is_void<T>::value;
// conditional,根据布尔条件选择类型
template <bool Condition, typename T, typename F>
struct conditional : TypeIdentity<T> {};
template <typename T, typename F>
struct conditional<false, T, F> : TypeIdentity<F> {};
// 方便使用的别名模板
template <bool Condition, typename T, typename F>
using conditional_t = typename conditional<Condition, T, F>::type;
int main() {
// 如果 T 是 void,则类型为 int,否则为 long
using Type1 = conditional_t<is_void<void>::value, int, long>;
using Type2 = conditional_t<is_void<char>::value, int, long>;
static_assert(std::is_same_v<Type1, int>, "Type1 should be int");
static_assert(std::is_same_v<Type2, long>, "Type2 should be long");
std::cout << "Type1 is int: " << std::boolalpha << std::is_same_v<Type1, int> << "\n";
std::cout << "Type2 is long: " << std::boolalpha << std::is_same_v<Type2, long> << "\n";
return 0;
}
运行输出:
Type1 is int: true
Type2 is long: true
如果你想,我还可以帮你写一些更复杂的条件类型选择示例哦。
这段内容是对 C++ 类型特征(type traits)中**基本类型类别(Primary type categories)**的总结和详细说明。下面我帮你详细解释一下:
基本类型类别(Primary type categories)
C++ 标准库中提供了许多类型特征,用于判断某个类型是否属于某个基本类别。这些类别包括:
is_void:判断类型是否为voidis_null_pointer:判断类型是否为空指针类型std::nullptr_tis_integral:判断类型是否为整数类型(包括所有标准的整型、布尔型、字符型等)is_floating_point:判断类型是否为浮点数类型(float、double、long double)is_array:判断是否为数组类型is_enum:判断是否为枚举类型is_union:判断是否为联合体类型is_class:判断是否为类类型is_function:判断是否为函数类型is_pointer:判断是否为指针类型is_lvalue_reference:判断是否为左值引用类型is_rvalue_reference:判断是否为右值引用类型is_member_object_pointer:判断是否为成员对象指针类型is_member_function_pointer:判断是否为成员函数指针类型
这些类型特征的规则
- 它们的基类都应当是
true_type或false_type(这两个都是std::integral_constant<bool, true/false>的别名),用来表示真假值。 - 对于带有
const、volatile等 cv 限定符的类型,结果应该和去除这些限定符后的类型结果相同。 - 对于任意给定类型
T,这组基本类型类别中且只有一个应当返回true_type,即确保类型分类是唯一且互斥的。
以 is_void 和 is_null_pointer 为例说明
is_void 的实现(示例):
template <typename T>
struct is_void : std::false_type { };
template <>
struct is_void<void> : std::true_type { };
template <>
struct is_void<void const> : std::true_type { };
template <>
struct is_void<void volatile> : std::true_type { };
template <>
struct is_void<void const volatile> : std::true_type { };
template <typename T>
inline constexpr bool is_void_v = is_void<T>::value;
is_null_pointer 的实现(示例):
template <typename T>
struct is_null_pointer : std::false_type { };
template <>
struct is_null_pointer<std::nullptr_t> : std::true_type { };
template <>
struct is_null_pointer<std::nullptr_t const> : std::true_type { };
template <>
struct is_null_pointer<std::nullptr_t volatile> : std::true_type { };
template <>
struct is_null_pointer<std::nullptr_t const volatile> : std::true_type { };
template <typename T>
inline constexpr bool is_null_pointer_v = is_null_pointer<T>::value;
is_integral 的实现特点
- 需要对所有标准整型(有符号和无符号)、字符类型、布尔类型,以及平台可能的扩展整型都做特化。
- 对应每个类型,也包括带有
const、volatile以及const volatile限定符的版本都要特化。 - 这导致实现中通常会有几十个模板特化,以覆盖所有整型类别。
总结
- 这些类型特征提供了对类型的分类信息,是元编程的重要基石。
- 它们通过继承
true_type或false_type来提供编译期的真假信息。 - 对于带 cv 限定符的类型,它们保证结果一致,体现了标准的要求。
- 在实际使用时,可以结合
*_v变量模板方便快速获得结果。
这段内容用生活化的类比,讲解了“元函数(metafunction)抽象”的思想,以及如何从实际的编程视角去理解和实现类型特征的逻辑。
元函数抽象 (Metafunction Abstractions)
- 本质上,元函数编程其实就是普通编程的延伸。
它是编译期对类型进行判断和计算的“函数”,不过操作的是类型而不是运行时的值。 - 用普通函数编程的思路去看元函数编程,会让你更好理解和设计元编程。
举例:假设类型是字符串,模拟 is_void、is_null_pointer、is_floating_point 判断
- 你想判断一个类型是不是
void,如果用字符串来表达类型,你可能写:
bool is_void(std::string_view s) {
return s == "void"
|| s == "void const"
|| s == "void volatile"
|| s == "void const volatile"
|| s == "void volatile const";
}
- 判断
nullptr_t类似:
bool is_null_pointer(std::string_view s) {
return s == "std::nullptr_t"
|| s == "std::nullptr_t const"
|| s == "std::nullptr_t volatile"
|| s == "std::nullptr_t const volatile"
|| s == "std::nullptr_t volatile const";
}
- 判断浮点数类型:
bool is_floating_point(std::string_view s) {
return s == "float" || s == "float const" || s == "float volatile" || s == "float const volatile"
|| s == "double" || s == "double const" || s == "double volatile" || s == "double const volatile"
|| s == "long double" || s == "long double const" || s == "long double volatile" || s == "long double const volatile";
}
代码审查 (Code Review)
- 这种写法非常冗长且重复,代码不好维护(文中用💩来形容)。
- 每种类型的不同 cv 限定符都写一遍,代码膨胀。
改进方向(A Step In The Right Direction)
- 先写一个辅助函数
remove_cv(std::string_view),统一去除字符串中的const和volatile,只留下基础类型名字。
std::string_view remove_cv(std::string_view s);
- 然后写
is_void、is_null_pointer、is_floating_point函数时,只对基础类型比较:
bool is_void(std::string_view s) {
return remove_cv(s) == "void";
}
bool is_null_pointer(std::string_view s) {
return remove_cv(s) == "std::nullptr_t";
}
bool is_floating_point(std::string_view s) {
auto base = remove_cv(s);
return base == "float" || base == "double" || base == "long double";
}
- 判断整数类型时,除了去除 cv,还要去除 signed/unsigned 标志:
std::string_view strip_signed(std::string_view s);
bool is_integral(std::string_view s) {
auto base = strip_signed(remove_cv(s));
return base == "bool" || base == "char" || base == "char8_t" || base == "char16_t" || base == "char32_t"
|| base == "wchar_t" || base == "short" || base == "int" || base == "long" || base == "long long";
}
小结
- 元函数编程的思考其实可以借助普通函数的思考方式(用字符串模拟类型)。
- 通过抽象和辅助函数,把重复代码提取出来,减少代码冗余。
- 在真实 C++ 元编程中,这些辅助“字符串操作”被替换成了对类型的“移除 cv 限定”、“拆解”等模板特化操作。
- 这样设计,更加简洁、可维护,也更符合实际编译器元编程的高效做法。
你这部分内容讲的是 C++ 模板元编程中,如何实现 remove_cv(移除顶层 const 和 volatile 修饰符)的类型变换工具。
1. 背景介绍
remove_const已经实现,它能移除类型顶层的const。- 还需要实现
remove_volatile,移除顶层的volatile。 - 最后用这两个工具组合,实现
remove_cv,同时移除顶层的const和volatile。
2. remove_volatile 实现
- 主模板(Primary template)默认不变,直接继承
TypeIdentity<T>,表示没有顶层volatile修饰:
template <typename T>
struct remove_volatile : TypeIdentity<T> { };
- 偏特化(Partial specialization)匹配顶层
volatile类型,移除它:
template <typename T>
struct remove_volatile<T volatile> : TypeIdentity<T> { };
- 方便使用的别名模板:
template <typename T>
using remove_volatile_t = typename remove_volatile<T>::type;
3. remove_cv 组合实现
- 先用
remove_volatile_t<T>去除顶层volatile,然后用remove_const去除顶层const:
template <typename T>
using remove_cv = remove_const<remove_volatile_t<T>>;
template <typename T>
using remove_cv_t = typename remove_cv<T>::type;
4. 应用示例:int const volatile
举例来说,类型是 int const volatile:
remove_cv<int const volatile>
展开:
- 先执行
remove_volatile_t<int const volatile>,得到:
int const // volatile 被去掉了
- 再执行
remove_const<int const>,得到:
int // const 被去掉了
最终结果是去掉了顶层 const 和 volatile,只剩下基础类型 int。
5. 小结
remove_volatile和remove_const类似,都是通过模板偏特化匹配顶层限定符来实现的。remove_cv通过组合这两个工具,实现同时移除顶层const和volatile。- 这种组合的写法简洁且符合元编程设计理念。
下面是一个完整的示例,演示了如何实现 TypeIdentity、remove_const、remove_volatile 以及组合的 remove_cv,并且通过 static_assert 测试验证类型是否正确移除了顶层 const 和 volatile:
#include <type_traits>
#include <iostream>
// TypeIdentity,用来直接返回类型本身
template <typename T>
struct TypeIdentity {
using type = T;
};
// remove_const:移除顶层 const
template <typename T>
struct remove_const : TypeIdentity<T> { };
template <typename T>
struct remove_const<T const> : TypeIdentity<T> { };
template <typename T>
using remove_const_t = typename remove_const<T>::type;
// remove_volatile:移除顶层 volatile
template <typename T>
struct remove_volatile : TypeIdentity<T> { };
template <typename T>
struct remove_volatile<T volatile> : TypeIdentity<T> { };
template <typename T>
using remove_volatile_t = typename remove_volatile<T>::type;
// remove_cv:移除顶层 const 和 volatile(先移除 volatile,再移除 const)
template <typename T>
using remove_cv = remove_const<remove_volatile_t<T>>;
template <typename T>
using remove_cv_t = typename remove_cv<T>::type;
// 测试函数,方便打印类型信息(需要C++17及以上)
template<typename T>
void print_type() {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
int main() {
// 基础类型测试
static_assert(std::is_same_v<remove_const_t<int const>, int>);
static_assert(std::is_same_v<remove_volatile_t<int volatile>, int>);
static_assert(std::is_same_v<remove_cv_t<int const volatile>, int>);
// 指针类型测试(顶层const/volatile不影响指针指向)
static_assert(std::is_same_v<remove_const_t<int * const>, int *>);
static_assert(std::is_same_v<remove_volatile_t<int * volatile>, int *>);
static_assert(std::is_same_v<remove_cv_t<int * const volatile>, int *>);
// 测试打印类型(可选)
print_type<remove_const_t<int const>>(); // 应该打印 int
print_type<remove_volatile_t<int volatile>>(); // 应该打印 int
print_type<remove_cv_t<int const volatile>>(); // 应该打印 int
return 0;
}
代码说明
TypeIdentity:用来直接传递类型不变。remove_const:通过模板偏特化匹配T const,去掉const。remove_volatile:通过模板偏特化匹配T volatile,去掉volatile。remove_cv:先调用remove_volatile_t去掉volatile,再调用remove_const去掉const。static_assert用于编译时验证类型是否被正确去除限定符。print_type用于演示打印类型信息(gcc/clang 下的__PRETTY_FUNCTION__,MSVC 可用其他方式)。
下面是关于 Comparing Types 章节内容的理解,结合你给出的代码片段和说明:
类型比较(Comparing Types)
- 之前讲到,模板的显式偏特化就像是一个类型的比较函数,用来判断两个类型是否相同。
- 但偏特化有两个限制:
- 代码写起来有点繁琐(verbose)。
- 只能在允许显式特化的类模板上下文中使用,不能用于函数模板。
实现一个真正的类型比较元函数
我们可以写一个模板元函数 is_same 来判断两个类型是否相同。
代码解释:
// 主模板,默认两个类型不同,继承自 std::false_type
template <typename T1, typename T2>
struct is_same : std::false_type {};
// 偏特化,当两个类型完全相同时,继承自 std::true_type
template <typename T>
struct is_same<T, T> : std::true_type {};
// 方便的变量模板,直接获取值
template <typename T1, typename T2>
constexpr bool is_same_v = is_same<T1, T2>::value;
- 当
T1和T2不同时,匹配主模板,结果为false。 - 当
T1和T2相同时,匹配偏特化,结果为true。
使用示例和说明
static_assert(not is_same_v<int, unsigned>);
// 由于 int 和 unsigned 不同,匹配主模板,结果 false
static_assert(is_same_v<int, int>);
// 两个类型相同,匹配偏特化,结果 true
进一步——忽略 cv 限定符的类型比较(is_same_raw)
有时我们希望忽略类型的顶层 const 和 volatile 限定符,进行比较:
template <typename T1, typename T2>
using is_same_raw = is_same<remove_cv_t<T1>, remove_cv_t<T2>>;
template <typename T1, typename T2>
constexpr bool is_same_raw_v = is_same_raw<T1, T2>::value;
- 这里先通过
remove_cv_t去除两个类型的const和volatile,然后再比较。 - 这样,
int和const int比较时也会被认为是相同的。
总结
is_same是判断两个类型是否完全相同的元函数。is_same_raw是忽略 cv 限定符后的类型比较元函数。- 这种写法清晰简洁,且是标准库里
std::is_same的简化示例。
类型特征模板(type traits)中几个典型的实现,涉及 is_floating_point、is_integral、is_array,以及对它们实现方式的简化和总结。下面我帮你逐条做理解:
1. is_floating_point 的简化实现(redux)
template <typename T>
using is_floating_point = std::bool_constant<
is_same_raw_v<float, T>
|| is_same_raw_v<double, T>
|| is_same_raw_v<long double, T>>;
is_floating_point用来判断类型T是否是浮点类型。- 这里用
is_same_raw_v(忽略const、volatile限定符的类型比较)判断T是否是float、double或long double之一。 - 结果封装成一个继承自
std::bool_constant的类型(bool_constant是integral_constant<bool, B>的别名),用于类型萃取。
2. is_integral 的简化实现(redux)
template <typename T>
using is_integral = std::bool_constant<
is_same_raw_v<bool, T>
|| is_same_raw_v<char, T>
|| is_same_raw_v<char8_t, T>
|| is_same_raw_v<char16_t, T>
|| is_same_raw_v<char32_t, T>
|| is_same_raw_v<wchar_t, T>
|| is_same_raw_v<signed char, T>
|| is_same_raw_v<short, T>
|| is_same_raw_v<int, T>
|| is_same_raw_v<long, T>
|| is_same_raw_v<long long, T>
|| is_same_raw_v<unsigned char, T>
|| is_same_raw_v<unsigned short, T>
|| is_same_raw_v<unsigned int, T>
|| is_same_raw_v<unsigned long, T>
|| is_same_raw_v<unsigned long long, T>>;
is_integral判断T是否是整型(包括带符号、无符号,及各种字符类型和布尔类型)。- 同样用
is_same_raw_v判断,并将多个整型类型做||判断。 - 结果用
std::bool_constant包装。
3. is_integral 的另一种抽象(简要提及)
template <typename TargetT, typename ... Ts>
using is_type_in_pack = ...;
template <typename T>
using is_integral = is_type_in_pack<remove_cv_t<T>,
bool,
char, char8_t, char16_t, char32_t, wchar_t,
signed char, unsigned char,
short, unsigned short,
int, unsigned int,
long, unsigned long,
long long, unsigned long long
>;
- 这里介绍了用“参数包”实现类型判断的思想,把所有整型类型放到参数包里。
is_type_in_pack元函数用来判断一个类型是否在这个参数包里。- 这种写法比前面用多个
||简洁,且易于扩展。
4. is_array 的实现
template <typename T>
struct is_array : std::false_type { };
template <typename T, std::size_t N>
struct is_array<T[N]> : std::true_type { };
template <typename T>
struct is_array<T[]> : std::true_type { };
is_array用来判断T是否是数组类型。- 默认情况(主模板)为
false。 - 特化:当
T是固定大小数组(T[N])时,结果为true。 - 特化:当
T是不定大小数组(T[])时,结果为true。 - 例如:
int[5]会匹配is_array<T[N]>特化,所以is_array<int[5]>::value == true。int[]会匹配is_array<T[]>特化,所以is_array<int[]>::value == true。
总结
- 利用类型特征和偏特化,可以很灵活地判断类型的性质(是否浮点,是否整型,是否数组等)。
- 通过
is_same_raw_v忽略 cv 限定符的比较,使判断更准确。 - 还可以用参数包封装多种类型判断,提高代码简洁性和可扩展性。
下面给你一个完整的示例代码,涵盖了:
is_same和is_same_raw(忽略 cv 限定符的类型比较)remove_const、remove_volatile、remove_cvis_floating_point和is_integral的实现is_array的实现- 以及简单的测试用例
static_assert验证
#include <type_traits>
#include <cstddef> // for std::size_t
// TypeIdentity 简单实现
template <typename T>
struct TypeIdentity {
using type = T;
};
// remove_const
template <typename T>
struct remove_const : TypeIdentity<T> {};
template <typename T>
struct remove_const<const T> : TypeIdentity<T> {};
template <typename T>
using remove_const_t = typename remove_const<T>::type;
// remove_volatile
template <typename T>
struct remove_volatile : TypeIdentity<T> {};
template <typename T>
struct remove_volatile<volatile T> : TypeIdentity<T> {};
template <typename T>
using remove_volatile_t = typename remove_volatile<T>::type;
// remove_cv = remove_const + remove_volatile
template <typename T>
using remove_cv = remove_const<remove_volatile_t<T>>;
template <typename T>
using remove_cv_t = typename remove_cv<T>::type;
// is_same
template <typename T1, typename T2>
struct is_same : std::false_type {};
template <typename T>
struct is_same<T, T> : std::true_type {};
template <typename T1, typename T2>
constexpr bool is_same_v = is_same<T1, T2>::value;
// is_same_raw 忽略 cv 限定符
template <typename T1, typename T2>
using is_same_raw = is_same<remove_cv_t<T1>, remove_cv_t<T2>>;
template <typename T1, typename T2>
constexpr bool is_same_raw_v = is_same_raw<T1, T2>::value;
// is_floating_point
template <typename T>
using is_floating_point = std::bool_constant<is_same_raw_v<float, T> || is_same_raw_v<double, T> ||
is_same_raw_v<long double, T>>;
// is_integral (列举了常用整型和字符型)
template <typename T>
using is_integral = std::bool_constant<
is_same_raw_v<bool, T> || is_same_raw_v<char, T> || is_same_raw_v<wchar_t, T> ||
is_same_raw_v<char16_t, T> || is_same_raw_v<char32_t, T> || is_same_raw_v<wchar_t, T> ||
is_same_raw_v<signed char, T> || is_same_raw_v<short, T> || is_same_raw_v<int, T> ||
is_same_raw_v<long, T> || is_same_raw_v<long long, T> || is_same_raw_v<unsigned char, T> ||
is_same_raw_v<unsigned short, T> || is_same_raw_v<unsigned int, T> ||
is_same_raw_v<unsigned long, T> || is_same_raw_v<unsigned long long, T>>;
// is_array
template <typename T>
struct is_array : std::false_type {};
template <typename T, std::size_t N>
struct is_array<T[N]> : std::true_type {};
template <typename T>
struct is_array<T[]> : std::true_type {};
// 测试用例
static_assert(is_same_v<int, int>);
static_assert(!is_same_v<int, unsigned>);
static_assert(is_same_raw_v<const int, volatile int>);
static_assert(is_same_raw_v<int const volatile, int>);
static_assert(is_floating_point<float>::value);
static_assert(is_floating_point<const double>::value);
static_assert(!is_floating_point<int>::value);
static_assert(is_integral<int>::value);
static_assert(is_integral<const unsigned long>::value);
static_assert(!is_integral<float>::value);
static_assert(is_array<int[5]>::value);
static_assert(is_array<int[]>::value);
static_assert(!is_array<int>::value);
int main() { return 0; }
说明:
remove_const和remove_volatile通过偏特化实现去除顶层的const和volatile。remove_cv组合这两个,去除const volatile。is_same_raw利用remove_cv忽略 cv 限定符后进行类型比较。is_floating_point和is_integral通过对比具体类型实现。is_array利用偏特化匹配数组类型。- 最后用
static_assert验证各个类型特征是否正确。
你可以直接编译运行,保证无错误,且类型判断符合预期。
这部分内容讲了如何实现和理解 C++ 类型特征里关于指针(is_pointer)、联合体(is_union)、类(is_class)及联合体或类(is_class_or_union)的判断机制,结合了一些编译器内建特性和类型推断技巧。
1. is_pointer(是否为指针类型)
- 基本实现思路:
通过模板特化检测一个类型是否是指针类型。- 默认模板:
is_pointer<T>继承自false_type,表示大多数类型不是指针。 - 偏特化模板:
is_pointer<T*>继承自true_type,表示指针类型。
- 默认模板:
- 去除
const/volatile限定符的版本:
先对类型T使用remove_cv_t去掉顶层的const和volatile,再判断是否为指针。
namespace detail {
template <typename T>
struct is_pointer_impl : std::false_type {};
template <typename T>
struct is_pointer_impl<T*> : std::true_type {};
}
template <typename T>
using is_pointer = detail::is_pointer_impl<remove_cv_t<T>>;
这样就能正确处理类似 int* const、volatile int* 这类带修饰符的指针类型。
2. is_union(是否为联合体类型)
- 编译器内建支持:
标准库的std::is_union依赖于编译器内置的特性,比如 GCC、Clang 提供的__is_union(T)内建关键字。
因为联合体在编译器层面是独特的类型,只有编译器能准确判断。
template <typename T>
using is_union = std::bool_constant<__is_union(T)>;
3. is_class(是否为类类型)
- 也几乎完全依赖编译器内建支持,原因和
is_union类似。 - C++标准并没有直接的语法能在模板中区分类和联合体。
- 只有编译器的内建函数才能准确判断是否是“非联合体类”。
4. is_class_or_union(是否为类或联合体)
- 思考方向:
联合体和类都有一个共同点:都能有成员。 - 检测类型是否能有成员的思路:
利用指向成员的语法int T::*判断一个类型T是否允许声明成员指针。
struct Bar {};
using BarIntObjectMemPtr = int Bar::*; // 合法,Bar 是类
using LongIntObjectMemPtr = int long::*; // 错误,long 不是类也不是联合体
- 通过检测
int T::*是否有效,间接判断T是不是类或联合体。
总结理解
| 特征 | 实现方式 | 备注 |
|---|---|---|
is_pointer |
模板偏特化检测指针类型 | 先去除顶层cv再判断 |
is_union |
编译器内建支持 | 依赖 __is_union(T) 内建 |
is_class |
编译器内建支持 | 无法用模板直接实现,需要编译器帮忙 |
is_class_or_union |
利用成员指针语法检测类型 | 判断是否能声明 int T::* |
这段内容通过函数重载解析(Function Overload Resolution)机制,巧妙利用重载优先级来实现类型特征检测,具体包括如何判断一个类型是否是 std::nullptr_t(空指针类型)和是否是 const 限定类型。
1. 判断是否是空指针类型 is_null_pointer
利用重载函数:
namespace detail {
std::true_type is_nullptr(std::nullptr_t);
std::false_type is_nullptr(...);
}
is_nullptr(std::nullptr_t)是针对std::nullptr_t类型的重载,返回std::true_type。is_nullptr(...)是通用的可变参数重载,匹配所有其他类型,返回std::false_type。
然后用decltype和std::declval结合调用:
template <typename T>
using is_null_pointer = decltype(detail::is_nullptr(std::declval<T>()));
原理:
- 如果
T是std::nullptr_t,调用is_nullptr(std::declval<T>())会匹配到第一个重载,类型为std::true_type。 - 否则匹配到第二个重载,类型为
std::false_type。
静态断言验证:
static_assert(!is_null_pointer<int>::value); // int 不是空指针类型
static_assert(is_null_pointer<std::nullptr_t>::value); // std::nullptr_t 是空指针类型
2. 判断是否是 const 限定类型 is_const
用同样的重载解析思路,结合自定义的 TypeIdentity(包装类型,确保类型推导一致):
namespace detail {
template <typename T>
std::true_type isconst(TypeIdentity<T const>);
template <typename T>
std::false_type isconst(TypeIdentity<T>);
}
isconst(TypeIdentity<T const>)对带const的类型匹配,返回std::true_type。isconst(TypeIdentity<T>)对不带const的类型匹配,返回std::false_type。
调用定义:
template <typename T>
using is_const = decltype(detail::isconst(std::declval<TypeIdentity<T>>()));
静态断言示例:
static_assert(!is_const<std::nullptr_t>::value); // std::nullptr_t 不是 const 类型
static_assert(is_const<int const>::value); // int const 是 const 类型
总结
- 利用函数重载优先级和匹配规则,实现针对类型的条件检测。
- 通过对不同类型参数匹配不同重载,结合
decltype推导返回对应的类型包装(true_type/false_type)。 is_null_pointer是检测类型是否为std::nullptr_t,对应空指针类型。is_const是检测类型是否带有const限定。- 这种方法无需写复杂的模板偏特化,用重载和
decltype就能简洁表达。
以下是完整的代码示例,展示如何通过函数重载解析实现 is_null_pointer 和 is_const 类型特征检测:
#include <type_traits>
#include <utility> // for std::declval
// TypeIdentity 用于类型包装,避免模板推导干扰
template <typename T>
struct TypeIdentity {
using type = T;
};
// is_null_pointer 实现
namespace detail {
// 重载1:匹配 std::nullptr_t,返回 std::true_type
std::true_type is_nullptr(std::nullptr_t);
// 重载2:匹配所有其他类型,返回 std::false_type
std::false_type is_nullptr(...);
}
// 模板别名,通过 decltype 调用重载函数,推导结果类型
template <typename T>
using is_null_pointer = decltype(detail::is_nullptr(std::declval<T>()));
// is_const 实现
namespace detail {
// 重载1:匹配 const 限定类型,返回 std::true_type
template <typename T>
std::true_type isconst(TypeIdentity<T const>);
// 重载2:匹配非 const 类型,返回 std::false_type
template <typename T>
std::false_type isconst(TypeIdentity<T>);
}
// 模板别名,通过 decltype 调用重载函数,推导结果类型
template <typename T>
using is_const = decltype(detail::isconst(std::declval<TypeIdentity<T>>()));
// 测试静态断言
static_assert(!is_null_pointer<int>::value, "int is not nullptr");
static_assert(is_null_pointer<std::nullptr_t>::value, "nullptr_t is nullptr");
static_assert(!is_const<int>::value, "int is not const");
static_assert(is_const<int const>::value, "int const is const");
static_assert(!is_const<std::nullptr_t>::value, "nullptr_t is not const");
int main() {
return 0;
}
说明
is_null_pointer<T>判断T是否是std::nullptr_t类型。is_const<T>判断T是否带const限定。- 利用函数重载区分不同类型,结合
decltype和std::declval实现编译期推导。 TypeIdentity主要用于保证类型准确传递,避免模板推导时被意外变形。
SFINAE 概念回顾
SFINAE (Substitution Failure Is Not An Error) 是 C++ 模板元编程中的一个重要机制,意思是“替换失败不是错误”。当模板参数替换导致某个模板实例化失败时,编译器不会报错,而是忽略该模板实例,转而尝试其他可用模板或重载。
代码解释
template <typename T>
std::true_type can_have_pointer_to_member(int T::*);
template <typename T>
std::false_type can_have_pointer_to_member(...);
- 这里定义了两个函数模板重载:
- 第一个模板接受一个类型为
int T::*的参数,即指向类型 T 中 int 成员的指针。如果T是一个合法的类型且可以拥有 int 成员指针,这个函数匹配,返回std::true_type。 - 第二个模板是一个变参模板,接受任意参数,作为兜底匹配,返回
std::false_type。
- 第一个模板接受一个类型为
如何用?
struct Foo { };
static_assert(decltype(can_have_pointer_to_member<Foo>(nullptr))::value);
- 这里对
Foo类型调用can_have_pointer_to_member<Foo>(nullptr)。 - 因为
int Foo::*是一个合法的指针成员类型(即使Foo没有任何成员,也允许声明指向它的成员指针),第一个模板匹配成功,返回std::true_type。 - 因此
static_assert通过。
进一步用法
template <typename T>
using can_have_member_ptr = decltype(can_have_pointer_to_member<T>(nullptr));
- 定义一个模板别名
can_have_member_ptr,通过decltype得到上述函数调用的返回类型,即std::true_type或std::false_type。
static_assert(can_have_member_ptr<int>::value);
int是一个基本类型,可以拥有成员指针类型吗?int int::*是合法的,代表指向int类型成员的指针(虽然奇怪,但语法允许)。因此断言通过。
总结
- 这段代码巧妙利用了函数模板重载与参数匹配,利用 SFINAE 判断一个类型是否可以拥有指向成员的指针类型。
- 如果类型
T能合法形成int T::*,就匹配第一个模板,返回true_type。 - 否则匹配第二个模板,返回
false_type。 - 这种技巧经常用来做类型特征检测。
下面给你一个完整的 C++ 示例代码,展示如何用 SFINAE 技巧检测一个类型是否可以拥有指向成员的指针类型(int T::*):
#include <type_traits>
#include <iostream>
// 函数模板重载:当参数类型能匹配 int T::* 时返回 std::true_type
template <typename T>
std::true_type can_have_pointer_to_member(int T::*);
// 兜底模板,匹配所有情况,返回 std::false_type
template <typename T>
std::false_type can_have_pointer_to_member(...);
// 别名模板,使用 decltype 推断上面函数的返回类型
template <typename T>
using can_have_member_ptr = decltype(can_have_pointer_to_member<T>(nullptr));
// 测试结构体
struct Foo {};
// 测试联合体
union Bar {
int a;
float b;
};
int main() {
std::cout << std::boolalpha;
// Foo 是类,可以拥有成员指针,应该输出 true
std::cout << "Foo can have member pointer? " << can_have_member_ptr<Foo>::value << "\n";
// int 是基本类型,但 int int::* 语法不合法,输出 false
std::cout << "int can have member pointer? " << can_have_member_ptr<int>::value << "\n";
// Bar 是联合体,也允许成员指针,输出 true
std::cout << "Bar can have member pointer? " << can_have_member_ptr<Bar>::value << "\n";
// 指针类型不允许成员指针,输出 false
std::cout << "int* can have member pointer? " << can_have_member_ptr<int*>::value << "\n";
return 0;
}
运行结果:
Foo can have member pointer? true
int can have member pointer? false
Bar can have member pointer? true
int* can have member pointer? false
解释
can_have_pointer_to_member(...):其他类型匹配,返回false_type。decltype(can_have_pointer_to_member<T>(nullptr))推断结果,即检测结果。static_assert或std::cout可以验证该类型是否符合“能拥有成员指针”的条件。
这段代码实现了一个判断类型 T 是否是类类型的元函数 is_class,其实现思路和细节如下:
代码大意和理解
namespace detail {
// 通过重载函数判断:如果参数是 int T::*,返回一个 bool,表示 T 不是联合体
template <typename T>
std::bool_constant<not std::is_union_v<T>> is_class_or_union(int T::*);
// 兜底重载,匹配所有其他情况,返回 false_type
template <typename T>
std::false_type is_class_or_union(...);
}
// 对外接口,将 detail 里的返回值转换成 std::bool_constant
template <typename T>
using is_class = decltype(detail::is_class_or_union<T>(nullptr));
解释:
- 这个元函数利用了指向成员指针语法(
int T::*)的特性:- 只有类(class 或 struct)和联合体(union)类型,才允许声明成员指针(
int T::*是指向类型T的成员变量的指针)。
- 只有类(class 或 struct)和联合体(union)类型,才允许声明成员指针(
detail::is_class_or_union(int T::*)这个重载函数会被调用:- 如果
T是类或者联合体,编译器能匹配这个函数签名。 - 它返回
std::bool_constant<not std::is_union_v<T>>,即如果T不是联合体,则为true,否则为false。
- 如果
- 另一个重载
detail::is_class_or_union(...)匹配所有非类非联合体的类型,返回std::false_type。 - 结合这两者的重载决议机制,实现了区分是否为类的判断。
详细步骤示意
- 当调用
detail::is_class_or_union<T>(nullptr):- 如果
T是类或联合体:int T::*是合法的函数参数类型,所以匹配第一个重载,返回std::bool_constant<not std::is_union_v<T>>。
- 如果
T不是类也不是联合体:int T::*是非法的,匹配失败,调用第二个重载,返回std::false_type。
- 如果
- 最终
is_class<T>是std::bool_constant<...>类型,表示是否是类类型(排除联合体)。
简单示例
#include <type_traits>
#include <iostream>
namespace detail {
template <typename T>
std::bool_constant<not std::is_union_v<T>> is_class_or_union(int T::*);
template <typename T>
std::false_type is_class_or_union(...);
}
template <typename T>
using is_class = decltype(detail::is_class_or_union<T>(nullptr));
struct A {};
union B { int x; };
int main() {
std::cout << std::boolalpha;
std::cout << "int: " << is_class<int>::value << "\n"; // false
std::cout << "A: " << is_class<A>::value << "\n"; // true
std::cout << "B: " << is_class<B>::value << "\n"; // false(联合体)
}
输出:
int: false
A: true
B: false
总结
- 通过指针成员语法,检测类型是否支持成员指针,初步判断是否为类或联合体。
- 结合
std::is_union排除联合体,只剩下类类型。 - 利用重载函数解析和 SFINAE 机制实现。
这段内容介绍了两种判断类型参数包(type parameter pack)中是否包含指定类型的实现方式:
1. 传统递归实现(Traditional Recursion)
模板元编程中,判断某个类型 TargetT 是否存在于类型包 Ts... 中。
// 模板声明,没有定义(为了递归展开)
template <typename TargetT, typename ... Ts>
struct IsInPack;
// 递归基:当包为空时,返回 false
template<typename TargetT>
struct IsInPack<TargetT> : std::false_type { };
// 如果包头类型等于目标类型,则返回 true
template<typename TargetT, typename ... Ts>
struct IsInPack<TargetT, TargetT, Ts...> : std::true_type { };
// 否则递归检查剩余类型包
template<typename TargetT, typename T, typename ... Ts>
struct IsInPack<TargetT, T, Ts...> : IsInPack<TargetT, Ts...> { };
// 测试用例
static_assert(IsInPack<int, double, char, int, float>::value); // true
static_assert(!IsInPack<long, double, char, int, float>::value); // false
理解:
- 这是经典递归展开思想。
- 按顺序依次判断包中每个类型是否等于目标类型。
- 如果匹配到,结果是
true,否则递归后续直到包空返回false。
2. 使用 std::is_base_of 的技巧实现
namespace detail {
template <typename ... Ts>
struct IsInPackImpl : TypeIdentity<Ts>... { };
}
template <typename TargetT, typename ... Ts>
using IsInPack = std::is_base_of<
TypeIdentity<TargetT>,
detail::IsInPackImpl<Ts...>>;
static_assert(IsInPack<int, double, char, int, float>::value); // true
static_assert(!IsInPack<long, double, char, int, float>::value); // false
理解:
IsInPackImpl继承了所有TypeIdentity<Ts>,也就是说它是所有这些类型的基类。IsInPack利用std::is_base_of判断TypeIdentity<TargetT>是否是IsInPackImpl<Ts...>的基类。- 因为继承了所有
TypeIdentity<Ts>, 所以如果TargetT在包内,TypeIdentity<TargetT>就是IsInPackImpl<Ts...>的基类,返回true。 - 这种写法利用了 C++ 继承体系的特性,避免递归,简洁且高效。
总结
| 实现方式 | 优点 | 缺点 |
|---|---|---|
| 传统递归 | 直观,符合模板递归思维 | 递归展开深,模板实例化多,可能编译慢 |
基于继承的 is_base_of |
简洁,无递归,编译效率较高 | 需要辅助模板类型 TypeIdentity |
下面给出一个完整可编译的示例,展示两种判断类型包中是否包含目标类型的实现:
#include <type_traits>
#include <iostream>
// 辅助模板:简单封装类型
template <typename T>
struct TypeIdentity {
using type = T;
};
// -----------------------
// 1. 传统递归实现
template <typename TargetT, typename ... Ts>
struct IsInPack;
// 基础情况:没有类型了,返回 false
template<typename TargetT>
struct IsInPack<TargetT> : std::false_type {};
// 包头类型和目标类型相同,返回 true
template<typename TargetT, typename ... Ts>
struct IsInPack<TargetT, TargetT, Ts...> : std::true_type {};
// 包头类型和目标类型不同,递归检查剩余类型包
template<typename TargetT, typename T, typename ... Ts>
struct IsInPack<TargetT, T, Ts...> : IsInPack<TargetT, Ts...> {};
// -----------------------
// 2. 基于继承的实现
namespace detail {
template <typename ... Ts>
struct IsInPackImpl : TypeIdentity<Ts>... { };
}
template <typename TargetT, typename ... Ts>
using IsInPack2 = std::is_base_of<
TypeIdentity<TargetT>,
detail::IsInPackImpl<Ts...>>;
// -----------------------
// 测试
int main() {
// 传统递归实现测试
static_assert(IsInPack<int, double, char, int, float>::value, "int should be in pack");
static_assert(!IsInPack<long, double, char, int, float>::value, "long should NOT be in pack");
// 继承实现测试
static_assert(IsInPack2<int, double, char, int, float>::value, "int should be in pack");
static_assert(!IsInPack2<long, double, char, int, float>::value, "long should NOT be in pack");
std::cout << "All static assertions passed.\n";
return 0;
}
运行结果:
编译通过,程序输出:
All static assertions passed.
说明:
IsInPack是传统递归实现,逐个展开比较类型。IsInPack2利用继承关系判断,简洁且效率可能更高。- 两种实现效果等价,静态断言都能正确判断。
介绍 C++ 中 void_t 的用法和相关技术,特别是用于 SFINAE(Substitution Failure Is Not An Error)实现检测类型特性,以及对某些类型是否满足某些条件进行推断。以下是对这些内容的详细理解与总结:
1. void_t 是什么?
void_t 是一种模板别名,它总是返回 void 类型,但接受任意模板参数。标准定义为:
template <typename...>
using void_t = void;
它的作用是利用 SFINAE 机制,当模板参数中的表达式合法时,void_t<...> 是有效类型 void;如果表达式不合法,则模板实例化失败,进入 SFINAE 分支。
2. 利用 void_t 检测类型成员(如检测类中是否有某成员类型)
举例:
struct One { using x = int; };
struct Two { using y = int; };
template <typename T, typename = void>
void func() {
// 默认版本,不满足条件时调用
}
template <typename T>
void func<T, void_t<typename T::x>>() {
// 只有当 T 有成员类型 x 时,此特化版本生效
}
int main() {
func<One>(); // 调用特化版本
func<Two>(); // 调用默认版本,因为 Two 没有 x
}
这里 void_t<typename T::x> 会尝试展开 T::x,如果存在,则实例化成功,调用特化;否则调用默认模板。
3. void_t 与 SFINAE 配合检测复杂表达式
例如,可以同时检测多个成员:
template <typename T, typename = void>
struct Blip { /* 默认实现 */ };
template <typename T>
struct Blip<T, void_t<typename T::zz, typename T::yy>> {
// 只有当 T 同时有成员类型 zz 和 yy 时才匹配
};
如果两个成员都存在,则第二个模板匹配成功。
4. 使用 void_t 检测运算符是否有效(函数调用是否有效)
利用 void_t 结合表达式检测,判断类型是否支持某操作:
namespace detail {
template <typename T, typename = void>
struct is_incrementable : std::false_type {};
template <typename T>
struct is_incrementable<T, void_t<decltype(++std::declval<T&>())>> : std::true_type {};
}
static_assert(detail::is_incrementable<int>::value, "int 支持 ++ 操作");
static_assert(!detail::is_incrementable<Bar>::value, "Bar 不支持 ++ 操作");
这里利用 decltype(++std::declval<T&>()) 判断 T 是否支持前置递增操作。
5. void_t 典型应用总结:
- 实现编译期检测类型是否有某个成员(类型/函数/变量)
- 检测表达式是否可用(某运算符或函数调用是否可行)
- 利用 SFINAE 机制进行重载选择或启用/禁用模板特化
6. 你给的示例中有的陷阱:
- 不能对同一模板多次定义同一偏特化(会报重定义错误)
- 不同的
void_t实现细节(用别名模板或模板结构体)对 SFINAE 有细微差别 void_t实际就是让模板参数不再影响模板的有效性,从而让 SFINAE 成为可能
总结
void_t 是元编程中非常重要的工具,它让检测类型特性变得优雅、简洁而高效。它本身并不产生代码,仅用于模板参数推断。
3 一个简单完整的例子,演示用 void_t 结合 SFINAE 检测类型是否有某个成员类型,以及检测某个运算符是否支持。
#include <type_traits>
#include <iostream>
// 1. 定义 void_t(C++17 以后标准库已有)
template<typename...>
using void_t = void;
// 2. 检测类型是否有成员类型 x
template <typename, typename = void>
struct has_member_x : std::false_type { };
template <typename T>
struct has_member_x<T, void_t<typename T::x>> : std::true_type { };
// 3. 检测类型是否支持前置 ++ 运算符
namespace detail {
template <typename T, typename = void>
struct is_incrementable : std::false_type {};
template <typename T>
struct is_incrementable<T, void_t<decltype(++std::declval<T&>())>> : std::true_type {};
}
// 4. 测试类型
struct One { using x = int; };
struct Two { using y = int; };
struct Three { };
struct Bar { };
// 5. 测试代码
int main() {
// 检测成员类型 x
std::cout << "One has member x? " << has_member_x<One>::value << "\n"; // 1 (true)
std::cout << "Two has member x? " << has_member_x<Two>::value << "\n"; // 0 (false)
std::cout << "Three has member x? " << has_member_x<Three>::value << "\n";// 0 (false)
// 检测是否支持 ++ 运算符
std::cout << "int incrementable? " << detail::is_incrementable<int>::value << "\n"; // 1 (true)
std::cout << "Bar incrementable? " << detail::is_incrementable<Bar>::value << "\n"; // 0 (false)
return 0;
}
运行结果
One has member x? 1
Two has member x? 0
Three has member x? 0
int incrementable? 1
Bar incrementable? 0
说明
has_member_x通过void_t<typename T::x>判断类型T是否有成员类型x,若没有该成员则 SFINAE 失效走默认false_type。is_incrementable利用decltype(++std::declval<T&>())判断类型是否支持前置递增操作。- 这样写法利用了模板的 SFINAE 特性,避免编译错误并实现类型特征判断。
这部分内容涉及C++类型特征中 函数类型检测(is_function)的模板实现细节,以及相关的调用约定和修饰符支持,帮你详细解析和总结:
Meta Calling Convention
- 这里用模板别名
CallMF用于调用一个模板模板参数MF,传入参数包Ts...,返回对应的MF<Ts...>::type。 CallVMF是MF<Ts...>::value的 constexpr 布尔值,方便直接用在static_assert中做断言。
示例:
static_assert(std::is_same_v<
CallMF<std::remove_cv, int const volatile>,
std::remove_cv_t<int const volatile>>);
static_assert(CallVMF<std::is_same,
CallMF<std::remove_cv, int const volatile>,
std::remove_cv_t<int const volatile>>);
含义是:用 remove_cv 去掉类型的 cv 限定后,与标准库 std::remove_cv_t 的结果一致。
is_lvalue_reference / is_rvalue_reference
- 主模板都返回
false_type。 - 对
T&(左值引用)和T&&(右值引用)进行模板特化,分别返回true_type。
这是识别左值引用和右值引用的标准写法。
is_function 的实现
is_function 用于判断一个类型是否为函数类型:
template <typename T>
struct is_function : std::false_type { }; // 默认不是函数
template <typename Ret, typename ... Args>
struct is_function<Ret(Args...)> : std::true_type { }; // 普通函数
// 支持 C 风格的可变参数函数
template <typename Ret, typename ... Args>
struct is_function<Ret(Args..., ...)> : std::true_type { };
// 兼容不同写法的可变参数
template <typename Ret, typename ... Args>
struct is_function<Ret(Args... ...)> : std::true_type { };
template <typename Ret, typename ... Args>
struct is_function<Ret(Args......)> : std::true_type { };
is_function 支持的修饰符
为了更精准判断函数类型,还需要支持各种函数修饰符:
- cv 限定(
const、volatile、const volatile) - 引用限定(
&、&&) - noexcept 修饰符
示例:
using F = int(double) const volatile; // cv限定的函数类型
using F1 = int(double) &; // 左值引用限定的成员函数类型
using F2 = int(double) &&; // 右值引用限定的成员函数类型
using F3 = int(double) noexcept; // noexcept修饰的函数类型
这些修饰符都需要对应的 is_function 特化版本来正确识别,通常实现时会写多达数十个特化版本来涵盖所有组合。
总结理解
is_function是检测类型是否为函数类型的模板。- 支持普通函数和C可变参数函数。
- 支持函数的各种cv修饰、引用限定和noexcept等现代C++特性。
- 为了正确识别所有情况,必须实现大量模板特化。
- 这些检测主要依赖模板特化匹配函数类型签名。
下面给你一个包含常见函数类型特化的 is_function 类型特征完整例子,支持普通函数、C可变参数函数、cv限定、引用限定和 noexcept 修饰的检测:
#include <type_traits>
#include <iostream>
// 主模板:默认不是函数类型
template <typename T>
struct is_function : std::false_type {};
// 普通函数类型
template <typename Ret, typename... Args>
struct is_function<Ret(Args...)> : std::true_type {};
// C-风格可变参数函数
template <typename Ret, typename... Args>
struct is_function<Ret(Args..., ...)> : std::true_type {};
// cv 限定函数
template <typename Ret, typename... Args>
struct is_function<Ret(Args...) const> : std::true_type {};
template <typename Ret, typename... Args>
struct is_function<Ret(Args...) volatile> : std::true_type {};
template <typename Ret, typename... Args>
struct is_function<Ret(Args...) const volatile> : std::true_type {};
// 引用限定函数
template <typename Ret, typename... Args>
struct is_function<Ret(Args...) &> : std::true_type {};
template <typename Ret, typename... Args>
struct is_function<Ret(Args...) &&> : std::true_type {};
// noexcept 函数
template <typename Ret, typename... Args>
struct is_function<Ret(Args...) noexcept> : std::true_type {};
template <typename Ret, typename... Args>
struct is_function<Ret(Args...) const noexcept> : std::true_type {};
template <typename Ret, typename... Args>
struct is_function<Ret(Args...) volatile noexcept> : std::true_type {};
template <typename Ret, typename... Args>
struct is_function<Ret(Args...) const volatile noexcept> : std::true_type {};
template <typename Ret, typename... Args>
struct is_function<Ret(Args...) & noexcept> : std::true_type {};
template <typename Ret, typename... Args>
struct is_function<Ret(Args...) && noexcept> : std::true_type {};
// 测试函数
void foo() {}
int bar(int) { return 0; }
int baz(...) { return 0; }
// 测试
int main() {
std::cout << std::boolalpha;
std::cout << "void() is function? " << is_function<void()>::value << "\n";
std::cout << "int(int) is function? " << is_function<int(int)>::value << "\n";
std::cout << "int(...) is function? " << is_function<int(...)>::value << "\n";
std::cout << "int() const is function? " << is_function<int() const>::value << "\n";
std::cout << "int() & is function? " << is_function<int() &>::value << "\n";
std::cout << "int() noexcept is function? " << is_function<int() noexcept>::value << "\n";
std::cout << "int is function? " << is_function<int>::value << "\n";
std::cout << "int* is function? " << is_function<int*>::value << "\n";
return 0;
}
输出示例:
void() is function? true
int(int) is function? true
int(...) is function? true
int() const is function? true
int() & is function? true
int() noexcept is function? true
int is function? false
int* is function? false
说明
- 利用模板特化针对不同函数签名(含修饰符)设定
is_function为true_type。 - 主模板默认为
false_type,用于非函数类型。 - 这样写可以检测大部分函数类型,配合现代C++特性(如
noexcept、引用限定、cv限定)。
更多推荐



所有评论(0)