使用类型擦除包装器解决概念自引用问题
类型擦除包装器构造函数的约束自引用问题及解决方案
问题描述
我实现了一个C++类型擦除包装器,其构造函数通过约束确保仅接受可构造std::decay_t<T>的参数:
#include <concepts> #include <type_traits> #include <utility> struct type_erasing_wrapper { template<typename T> requires( not std::same_as<std::remove_cvref_t<T>, type_erasing_wrapper> and std::constructible_from<std::decay_t<T>, T> ) type_erasing_wrapper(T&& s) : ptr{new std::decay_t<T>(std::forward<T>(s))}, destroy{ [](void* ptr) noexcept { delete static_cast<std::decay_t<T>*>(ptr); } } {} // 移动构造函数、赋值运算符及析构函数... void* ptr; void(*destroy)(void*) noexcept; };
原本约束工作正常,但当引入如下holder结构体并添加静态断言后,Clang/GCC提示构造函数约束存在自引用问题:
template<typename T> struct holder { explicit holder(T object) : object{std::move(object)} {} T object; }; static_assert(std::constructible_from<type_erasing_wrapper, holder<type_erasing_wrapper>>);
我尝试修改holder为模板构造版本,但兼容性因编译器版本而异;改为聚合类可兼容所有编译器,但我不想限制holder的实现方式。
问题求解
- 除修改
holder外,是否有不修改API且符合标准的解决方案? - MSVC可编译所有示例,这是否是Clang/GCC的bug?
解决方案(不修改holder)
问题根源在于:当T = holder<type_erasing_wrapper>时,检查std::constructible_from<std::decay_t<T>, T>会触发holder<type_erasing_wrapper>的构造合法性验证,而holder的构造需要初始化type_erasing_wrapper成员,这又会递归检查type_erasing_wrapper的构造约束,形成无限循环。
要解决这个问题,只需将约束中的concept式检查替换为类型 trait式检查,避免触发递归的concept验证:
template<typename T> requires( not std::same_as<std::remove_cvref_t<T>, type_erasing_wrapper> and std::is_constructible_v<std::decay_t<T>, T> ) type_erasing_wrapper(T&& s) : ptr{new std::decay_t<T>(std::forward<T>(s))}, destroy{ [](void* ptr) noexcept { delete static_cast<std::decay_t<T>*>(ptr); } } {}
std::is_constructible_v是编译期常量表达式,求值过程不会触发concept的递归约束检查,因此能打破循环。
关于编译器行为的说明
MSVC可编译并非Clang/GCC的bug,而是不同编译器对约束递归的处理策略差异:
- Clang/GCC严格遵循C++标准中关于约束求值的规则,当检测到无限递归的约束检查时,直接报错终止编译。
- MSVC采用了更宽松的递归处理逻辑,可能在递归到一定深度后终止检查并判定约束满足,但这种行为并不符合标准要求。
内容的提问来源于stack exchange,提问作者Guillaume Racicot
相关产品推荐
相关产品推荐

