如何为无默认构造函数的类型实现整洁的declval替代方案?
编译期推导容器最深元素类型:解决无默认构造类型的问题
问题背景
我实现了一个C++20模板,用于递归推导嵌套容器的最深层元素类型,核心代码如下:
template<typename T> concept Iteratable = requires(T n) { n.begin(); n.end(); }; namespace detail { template<Iteratable T> using subtype = std::decay_t<decltype(*(std::declval<T>().begin()))>; template<Iteratable T> constexpr auto deepest_subtype_recursive() { if constexpr (Iteratable<subtype<T>>) { return detail::deepest_subtype_recursive<subtype<T>>(); } else { return subtype<T>{}; } } } template<Iteratable T> using deepest_subtype = decltype(detail::deepest_subtype_recursive<T>());
这个模板能正确处理std::vector<std::list<std::deque<int>>>这类容器,推导出最深类型int。但当元素类型没有默认构造函数时(比如自定义的foo结构),代码会编译失败:
struct foo { foo(int) {} }; int main() { using container_type = std::vector<std::list<std::deque<foo>>>; using deepest = deepest_subtype<container_type>; static_assert(std::is_same_v<deepest, foo>); }
报错信息:
error C2512: 'foo': no appropriate default constructor available
如果尝试改用std::declval<subtype<T>>()返回,会触发另一个错误:
error C2338: static_assert failed: 'Calling declval is ill-formed, see N4892 [declval]/2.'
这是因为std::declval仅允许在未求值语境(如decltype、requires表达式)中使用,不能在constexpr函数的return语句这类求值语境下调用。
我自行实现了一个类似declval的临时方案,但不够整洁:
template<typename T> constexpr auto declval() { using pointer = T*; return *(pointer{}); }
解决方案
方案一:利用std::type_identity包装类型(最小修改)
C++20引入的std::type_identity可用来包装目标类型,它的默认构造不受模板参数的构造函数限制。修改递归函数返回std::type_identity<subtype<T>>实例,再通过其type成员获取最终类型:
#include <type_traits> template<typename T> concept Iteratable = requires(T n) { n.begin(); n.end(); }; namespace detail { template<Iteratable T> using subtype = std::decay_t<decltype(*(std::declval<T>().begin()))>; template<Iteratable T> constexpr auto deepest_subtype_recursive() { if constexpr (Iteratable<subtype<T>>) { return deepest_subtype_recursive<subtype<T>>(); } else { return std::type_identity<subtype<T>>{}; } } } template<Iteratable T> using deepest_subtype = typename decltype(detail::deepest_subtype_recursive<T>())::type;
方案二:纯类型元编程(推荐)
改用模板特化和递归类型别名,完全避免函数调用和对象构造的问题,这是更符合C++元编程风格的常规方案:
#include <type_traits> template<typename T> concept Iteratable = requires(T n) { n.begin(); n.end(); }; namespace detail { template<Iteratable T> using subtype = std::decay_t<decltype(*(std::declval<T>().begin()))>; // 基础模板声明 template<typename T> struct deepest_subtype_impl; // 递归特化:类型可迭代时,继续推导子类型 template<Iteratable T> struct deepest_subtype_impl<T> { using type = typename deepest_subtype_impl<subtype<T>>::type; }; // 终止特化:类型不可迭代时,返回自身 template<typename T> requires (!Iteratable<T>) struct deepest_subtype_impl<T> { using type = T; }; } template<Iteratable T> using deepest_subtype = typename detail::deepest_subtype_impl<T>::type;
方案说明
- 方案一修改量极小,仅调整返回值包装方式和最终类型获取逻辑,无需重构核心递归逻辑。
- 方案二更简洁安全,完全基于类型系统操作,无任何对象构造风险,也规避了
std::declval的使用限制,是编译期类型推导的标准做法。 - 自行实现的
declval方案存在未定义行为风险:解引用空指针属于标准未定义行为,即使当前编译器允许,也无法保证跨编译器或未来版本的兼容性。
内容的提问来源于stack exchange,提问作者Stack Danny
相关产品推荐
相关产品推荐

