跳到主要内容
左猫娘右猫娘

MSVC屎山代码发力了→requires前向声明问题

· 阅读需 5 分钟
Heng_Xin
ここから先は一方通行だ!

MSVC屎山代码发力了, 真无敌了! 明明只是一个 requires 的二阶段查找的问题 なのに...

实际上后面发现是 前向声明 问题, 当然, 实际上也利用了 二阶段名称查找

一、缘起

哥们最近在为项目写了Web框架, 方便控制器的定义和使用等等, 并且支持依赖注入 (实际上就是传参)

#include <HXLibs/net/ApiMacro.hpp>

struct LoliDAO {
uint64_t select(uint64_t id) {
return id;
}
};

HX_CONTROLLER(LoliController) {
HX_ENDPOINT_MAIN(std::shared_ptr<LoliDAO> loliDAO) {
using namespace HX::net;
addEndpoint<GET>("/", [=] ENDPOINT {
auto id = loliDAO->select(114514);
co_await res.setStatusAndContent(Status::CODE_200, std::to_string(id))
.sendRes();
})
.addEndpoint<POST>("/post", [=] ENDPOINT {
auto id = loliDAO->select(2233);
co_await res.setStatusAndContent(Status::CODE_200, std::to_string(id))
.sendRes();
});
}
};

#include <HXLibs/net/UnApiMacro.hpp>

int main() {
using namespace HX::net;
HttpServer server{28205};
// 依赖注入
server.addController<LoliController>(std::make_shared<LoliDAO>());
server.syncRun(1);
}
cpp

其中 addController 的实现是:

/**
* @brief 注册控制器到服务器, 可以进行依赖注入, 依次传参即可
* @tparam T 控制器类型
* @tparam Args
* @param args 被依赖注入的变量
*/
template <typename T, typename... Args>
requires (std::is_base_of_v<class BaseController, T>)
inline HttpServer& HttpServer::addController(Args&&... args) {
T {*this}.dependencyInjection(std::forward<Args>(args)...);
return *this;
}
cpp

因为 class BaseController 的实现暂时是不可见, 但是在正确的使用下, BaseController 就是可见的了.

总之, 这里我期望使用 二阶段查找 实现约束控制器类型. 期望用户通过宏编写的 控制器 才是有效的.

确实, GCC 14+ / Clang17+ 都是正确的. 但 MSVC ? => https://godbolt.org/z/Kq7Gc5hs1 直接没有匹配的函数好吧.

二、描述

最小复现案例:

#include <type_traits>
#include <concepts>

struct Test {
template <typename T>
requires (requires (T& t) {
{ static_cast<class Base&>(t) };
})
void func() {}
};

class Base {};
class A : public Base {};

int main() {
Test{}.func<A>();
}
cpp

然后就更厉害了, 我一不小心的尝试, 发现 MS💩VC 就是 巨大的 💩 山!

三、💩

我的本意是希望说, { static_cast<class Base&>(t) } 实际上就是 std::is_base_of_v<class Base, T> 的意思:

AUV, 您猜怎么着?

#include <print>
#include <type_traits>
#include <concepts>

struct Test {
template <typename T>
requires (requires (T& t) {
{ static_cast<class Base&>(t) };
})
void func1() {}

template <typename T>
requires (std::is_base_of_v<class Base, T>)
void func2() {}
};

class Base {};
class A : public Base {};

int main() {
Test{}.func2<A>(); // 没有报错, 编译通过!
}
cpp

我就纳闷了, 怎么回事?!

我把 func1 删除, 然后就又报错了...

最终, 我把 func1func2 位置交换:

#include <print>
#include <type_traits>
#include <concepts>

struct Test {
template <typename T>
requires (std::is_base_of_v<class Base, T>)
void func2() {}

template <typename T>
requires (requires (T& t) {
{ static_cast<class Base&>(t) };
})
void func1() {}
};

class Base {};
class A : public Base {};

int main() {
Test{}.func1<A>(); // 正确
// ps: func2, MSVC 会编译报错
}
cpp

你这不是扯淡吗? MSVC 内部不会是写的 💩 吧?

// 可能的 MSVC 内部呆码:
if (classFunc[0] == ???) {
// mscv 专属操作 todo ...
} else {
// 通用操作 todo ...
}
cpp

emm, 不对. 没有那么复杂, 实际上是 前向声明的问题. MSVC 好像抽风了, 它把 上一个函数的 requires 块 的 class Base 当做前向声明

而不把自己的 requires 内容当做前向声明.

struct Test {
template <typename T>
requires (requires (T& t) {
{ static_cast<class Base&>(t) }; // 这个 class Base 被func2当做前向声明, func1 没有看到 class Base
})
void func1() {}

template <typename T>
requires (std::is_base_of_v<class Base, T>)
void func2() {}
};

func1(); // err
func2(); // ok: 看到 func1 的 class Base
cpp

四、解决方案

requires 提到 函数体前; 而不是 requires 紧跟 template 后 (这种写法叫 template-head requires clause)

struct Test {
template <typename T>
void func2() requires (requires (T& t) {
{ static_cast<class Base&>(t) };
}) {}
};
cpp

他们都是等价的 (但是 MSVC 可能会有bug, 比如这里...)

你不需要修改写法, 怎么顺眼怎么来吧? msvc-std 的 tuple 也是 template-head requires clause 写法的...


  • 随意丢弃到: P10992381 他们爱修不修吧...
请作者喝奶茶:
Alipay IconQR Code
Alipay IconQR Code
本文遵循 CC CC 4.0 BY-SA 版权协议, 转载请标明出处
Loading Comments...