clice 是基于现代 C++(C++23)的新一代 C++ 语言服务器,其目标是超越 clangd。目前 clice 正处于开发早期,尚未发布 release 版本。
clice 使用了大量现代 C++ 语法,具有极高的学习价值。本文尝试分析 clice 中使用的部分元编程技术。
Struct.h
在 include/Support/Struct.h 中有与结构体相关的工具,例如 member_count 函数可以获取结构体的成员数量,member_name 函数可以根据结构体成员的地址获取成员的名称。
首先分析 member_count,核心代码如下:
struct Any {
consteval Any(std::size_t);
template <typename T>
consteval operator T () const;
};
template <typename T, std::size_t N>
consteval auto test() {
return []<std::size_t... I>(std::index_sequence<I...>) {
return requires { T{Any(I)...}; };
}(std::make_index_sequence<N>{});
}
template <typename T, std::size_t N = 0>
consteval auto member_count() {
if constexpr(test<T, N>() && !test<T, N + 1>()) {
return N;
} else {
return member_count<T, N + 1>();
}
}代码首先定义了 Any 结构体。它通过一个 std::size_t 类型的值构造,并且可以隐式转换为任意类型。Any 的构造函数和类型转换函数都被标记为 consteval,这意味着它们必须在编译期被调用,在运行期的调用将会引发编译错误。这保证了相关代码一定会在编译期执行,不会造成任何运行开销。
test 函数用于检查是否能用 N (第二个模板参数)个 Any 对象构造模板类型 T(第一个模板参数)对象。用一个例子方便理解:
struct Test {
int a;
double b;
char c;
};
int main() {
constexpr auto b = test<Test, 3>();
std::println("{}", b);
}以上代码会输出 true。
Test 对象可以用零个、一个、两个或三个 Any 对象初始化,但不能超过三个,如下:
consteval void func() {
Test t1{Any(0)};
Test t2{Any(0), Any(1)};
Test t3{Any(0), Any(1), Any(2)};
Test t4{Any(0), Any(1), Any(2), Any(4)}; // compile error
}这比较好理解,因为 Test 结构体只有三个成员。用一个 Any 对象初始化 t1 时,Any对象会隐式转换为 int 类型,t1 剩余的两个成员将被值初始化。用两个 Any 对象初始化 t2 时,第一个 Any 对象会隐式转换为 int 类型,第二个会隐式转换为 double 类型,t2 剩余的成员 c 将被值初始化。三个 Any 对象时以此类推。当使用四个 Any 对象时,会因为初始化的值多于 Test 的成员数量而报错。
接下来看 test 函数:
template <typename T, std::size_t N>
consteval auto test() {
return []<std::size_t... I>(std::index_sequence<I...>) {
return requires { T{Any(I)...}; };
}(std::make_index_sequence<N>{});
}test 函数的主体是一个 return 语句,它返回一个 lambda 表达式的调用结果。这个 lambda 表达式接收一个 std::index_sequence<I...> 类型的参数,返回 requires { T{Any(I)...}; } 的值,在 lambda 表达式最后的括号里,传入了 std::make_index_sequence
实参 std::make_index_sequenceT{Any(I)...}; 这段代码展开了参数包,形成了类似 T{Any(0), Any(1), Any(2), ... , Any(N - 1)}; 的代码。包裹在外面的 requires {} 是 C++20 新增的语法特性,叫做 requires 子句。它能够检查花括号内的代码能否通过编译,如果能通过编译,则返回 true,否则返回 false。在这里,它将检查 T{Any(0), Any(1), Any(2), ... , Any(N - 1)}; 能否通过编译。如果 N 的值小于等于结构体 T 的成员数量,那么 requires { T{Any(I)...}; } 将返回 true,否则返回 false。
现在回过头重新看这句话:test 函数用于检查是否能用 N (第二个模板参数)个 Any 对象构造模板类型 T(第一个模板参数)对象。是不是理解了?
最后看 member_count 函数
template <typename T, std::size_t N = 0>
consteval auto member_count() {
if constexpr(test<T, N>() && !test<T, N + 1>()) {
return N;
} else {
return member_count<T, N + 1>();
}
}这段代码通过递归寻找最大的 N,也就是 T 的成员数量。当找到最大的 N,就返回它的值,否则就将 N 加一,继续寻找。最终 member_count 能够返回结构体 T 的成员数量。
下面来分析 member_name。member_name 函数可以根据结构体成员的地址得到成员的名称。代码如下:
template <typename T>
struct wrapper {
T value;
constexpr wrapper(T value) : value(value) {}
};
template <wrapper T>
consteval auto member_name() {
std::string_view name = std::source_location::current().function_name();
#if __GNUC__ && (!__clang__) && (!_MSC_VER)
std::size_t start = name.rfind("::") + 2;
std::size_t end = name.rfind(')');
name = name.substr(start, end - start);
#elif __clang__
std::size_t start = name.rfind(".") + 1;
std::size_t end = name.rfind('}');
name = name.substr(start, end - start);
#elif _MSC_VER
std::size_t start = name.rfind("->") + 2;
std::size_t end = name.rfind('}');
name = name.substr(start, end - start);
#else
static_assert(false, "Not supported compiler");
#endif
if(name.rfind("::") != std::string_view::npos) {
name = name.substr(name.rfind("::") + 2);
}
return name;
}member_name 的原理比较简单粗暴,使用 std::source_location 获取当前函数的名称,从函数名称中截取出结构体成员名。
member_name 的用法如下:
static struct X {
int a;
int b;
} x;
static_assert(member_name<&x.a>() == "a", "Member name mismatch");
static_assert(member_name<&x.b>() == "b", "Member name mismatch");这里有一点需要注意,那就是 member_name 的模板参数值。在模板编程中,模板参数一般是 int、std::size_t 等类型,或者是枚举值、整数字面量,但是在这里,模板参数却是结构体成员的地址。
众所周知,模板参数必须在编译期指定,而变量地址在运行期才能确定,为什么这里不会有编译错误?这是因为结构体 x 被标记为 static,拥有静态存储期,编译器可以将其地址作为编译期值。可以尝试下面的代码验证这一点:
int main() {
static int x;
constexpr auto addr = &x;
}更详细的解释可以看知乎上的这篇回答。
14 条评论
华纳公司官方开户渠道?(183-8890-9465)-薇-STS5099【6011643】
如何通过官方渠道申请华纳公司账户?(183-8890-9465)-薇-STS5099【6011643】
华纳总公司官方开户指南?(183-8890-9465)-薇-STS5099【6011643】
华纳公司官方开户所需材料?(183-8890-9465)-薇-STS5099【6011643】
华纳官方开户流程?(183-8890-9465)-薇-STS5099【6011643】
华纳公司官方开户申请步骤?(183-8890-9465)-薇-STS5099【6011643】
华纳官方开户指南?(183-8890-9465)-薇-STS5099【6011643】
华纳总公司官方开户?(183-8890-9465)-薇-STS5099【6011643】
华纳公司官方开户所需材料?(183-8890-9465)-薇-STS5099【6011643】
华纳官方开户申请流程?(183-8890-9465)-薇-STS5099【6011643】
华纳圣淘沙公司开户新手教程
零基础学会(183-8890-9465薇-STS5099)
华纳圣淘沙公司开户
华纳圣淘沙公司开户保姆级教程(183-8890-9465薇-STS5099)
一步步教你开通华纳圣淘沙公司账户(183-8890-9465薇-STS5099)
华纳圣淘沙公司开户分步图解
首次开户必看:(183-8890-9465薇-STS5099)
华纳圣淘沙全攻略
华纳圣淘沙公司开户实操手册(183-8890-9465薇-STS5099)
华纳圣淘沙开户流程视频教程
手把手教学:(183-8890-9465薇-STS5099)
华纳圣淘沙公司开户
华纳圣淘沙公司开户完全指南(183-8890-9465薇-STS5099)
华纳圣淘沙公司开户新手教程
零基础学会(183-8890-9465薇-STS5099)
华纳圣淘沙公司开户
华纳圣淘沙公司开户保姆级教程(183-8890-9465薇-STS5099)
一步步教你开通华纳圣淘沙公司账户(183-8890-9465薇-STS5099)
华纳圣淘沙公司开户分步图解
首次开户必看:(183-8890-9465薇-STS5099)
华纳圣淘沙全攻略
华纳圣淘沙公司开户实操手册(183-8890-9465薇-STS5099)
华纳圣淘沙开户流程视频教程
手把手教学:(183-8890-9465薇-STS5099)
华纳圣淘沙公司开户
华纳圣淘沙公司开户完全指南(183-8890-9465薇-STS5099)
华纳圣淘沙公司快速开户通道(183-8890-9465—?薇-STS5099【6011643】
三分钟搞定华纳圣淘沙公司开户
(183-8890-9465—?薇-STS5099【6011643】
华纳圣淘沙公司极速开户攻略(183-8890-9465—?薇-STS5099【6011643】
华纳圣淘沙开户流程提速秘籍(183-8890-9465—?薇-STS5099【6011643】
如何快速完成华纳圣淘沙公司注册(183-8890-9465—?薇-STS5099【6011643】
华纳圣淘沙开户步骤详解(183-8890-9465—?薇-STS5099【6011643】
华纳圣淘沙公司开户流程全解析(183-8890-9465—?薇-STS5099【6011643】
华纳圣淘沙公司账户注册指南(183-8890-9465—?薇-STS5099【6011643】
新手如何开通华纳圣淘沙公司账户(183-8890-9465—?薇-STS5099【6011643】
华纳圣淘沙企业开户标准流程(183-8890-9465—?薇-STS5099【6011643】
华纳圣淘沙公司开户:从零到一(183-8890-9465—?薇-STS5099【6011643】
官方指南:华纳圣淘沙公司开户流程(183-8890-9465—?薇-STS5099【6011643】
华纳圣淘沙公司开户流程说明书(183-8890-9465—?薇-STS5099【6011643】
华纳圣淘沙开户步骤详解(183-8890-9465—?薇-STS5099【6011643】
华纳圣淘沙公司开户流程全解析(183-8890-9465—?薇-STS5099【6011643】
华纳圣淘沙公司账户注册指南(183-8890-9465—?薇-STS5099【6011643】
新手如何开通华纳圣淘沙公司账户(183-8890-9465—?薇-STS5099【6011643】
华纳圣淘沙企业开户标准流程(183-8890-9465—?薇-STS5099【6011643】
华纳圣淘沙公司开户:从零到一(183-8890-9465—?薇-STS5099【6011643】
官方指南:华纳圣淘沙公司开户流程(183-8890-9465—?薇-STS5099【6011643】
华纳圣淘沙公司开户流程说明书(183-8890-9465—?薇-STS5099【6011643】
东方明珠客服开户联系方式【182-8836-2750—】?μ- cxs20250806
东方明珠客服电话联系方式【182-8836-2750—】?- cxs20250806】
东方明珠开户流程【182-8836-2750—】?薇- cxs20250806】
东方明珠客服怎么联系【182-8836-2750—】?薇- cxs20250806】
果博东方客服开户联系方式【182-8836-2750—】?薇- cxs20250806】
果博东方公司客服电话联系方式【182-8836-2750—】?薇- cxs20250806】
果博东方开户流程【182-8836-2750—】?薇- cxs20250806】
果博东方客服怎么联系【182-8836-2750—】?薇- cxs20250806】
新盛客服电话是多少?(?183-8890-9465—《?薇-STS5099】【
新盛开户专线联系方式?(?183-8890--9465—《?薇-STS5099】【?扣6011643??】
新盛客服开户电话全攻略,让娱乐更顺畅!(?183-8890--9465—《?薇-STS5099】客服开户流程,华纳新盛客服开户流程图(?183-8890--9465—《?薇-STS5099】
华纳东方明珠客服电话是多少?(??155--8729--1507?《?薇-STS5099】【?扣6011643?】
华纳东方明珠开户专线联系方式?(??155--8729--1507?《?薇-STS5099】【?扣6011643?】
华纳东方明珠客服电话是多少?(▲18288362750?《?微信STS5099? 】
如何联系华纳东方明珠客服?(▲18288362750?《?微信STS5099? 】
华纳东方明珠官方客服联系方式?(▲18288362750?《?微信STS5099?
华纳东方明珠客服热线?(▲18288362750?《?微信STS5099?
华纳东方明珠24小时客服电话?(▲18288362750?《?微信STS5099? 】
华纳东方明珠官方客服在线咨询?(▲18288362750?《?微信STS5099?
华纳东方明珠客服电话是多少?(▲18288362750?《?微信STS5099? 】【╃q 2704132802╃】
华纳东方明珠开户专线联系方式?(▲18288362750?《?微信STS5099? 】【╃q 2704132802╃】
如何联系华纳东方明珠客服?(▲18288362750?《?微信STS5099? 】【╃q 2704132802╃】
华纳东方明珠官方客服联系方式?(▲18288362750?《?微信STS5099? 】【╃q 2704132802╃】
华纳东方明珠客服热线?(▲18288362750?《?微信STS5099? 】【╃q 2704132802╃】
华纳东方明珠开户客服电话?(▲182(▲18288362750?《?微信STS5099? 】【╃q 2704132802╃】
华纳东方明珠24小时客服电话?(▲18288362750?《?微信STS5099? 】【╃q 2704132802╃】
华纳东方明珠客服邮箱?(▲18288362750?《?微信STS5099? 】【╃q 2704132802╃】
华纳东方明珠官方客服在线咨询?(▲18288362750?《?微信STS5099? 】【╃q 2704132802╃】
华纳东方明珠客服微信?(▲18288362750?《?微信STS5099? 】【╃q 2704132802╃】
华纳总公司开户申请步骤?(▲18288362750?《?微信STS5099? 】【╃q 2704132802╃】
华纳公司合作开户所需材料?电话号码15587291507 微信STS5099
华纳公司合作开户所需材料?电话号码15587291507 微信STS5099
华纳公司合作开户所需材料?电话号码15587291507 微信STS5099
华纳公司合作开户所需材料?电话号码15587291507 微信STS5099
华纳公司合作开户所需材料?电话号码15587291507 微信STS5099
华纳公司合作开户所需材料?电话号码15587291507 微信STS5099
华纳公司合作开户所需材料?电话号码15587291507 微信STS5099
华纳公司合作开户所需材料?电话号码15587291507 微信STS5099