如何为模板化容器条件式提供分配器支持?
可模板化容器的分配器适配问题
我正在实现一个可模板化的容器,其成员字符串类型支持拥有型(std::string)或非拥有型(std::string_view)。对于非拥有型字符串,无需也无法提供分配器,因此需要编译器根据字符串类型自动生成对应构造函数:带分配器版本(针对拥有型字符串)、不带分配器版本(针对非拥有型字符串)。
以下是我的尝试实现:
#include <iostream> #include <string_view> #include <memory> #include <string> template <typename T> concept uses_allocator_general = requires { typename T::allocator_type; }; template <typename StringType = std::string> struct URL { using allocator_type = std::conditional_t<uses_allocator_general<StringType>, typename StringType::allocator_type, void>; URL(allocator_type allocator = {}) : mystr{allocator} { } StringType mystr; }; int main() { URL myurl; URL<std::string_view> myurl_view; }
当前存在三个待解决的问题:
- 代码无法编译:
StringType::allocator_type会在std::conditional_t的条件判断前被求值,导致std::string_view实例化时报错,需要用间接方式或SFINAE条件性获取类型定义。 - 即使编译通过,能否通过将参数设为
void来移除该参数?我认为这不可行,有哪些替代方案? - 是否存在更优雅的解决方案?
解决方案
1. 解决类型提前求值问题
std::conditional_t的两个分支会被强制实例化,因此当StringType不具备allocator_type时(如std::string_view),typename StringType::allocator_type会触发编译错误。我们需要用延迟实例化的方式避免这个问题,C++20的concept是最直接的方案。
首先定义更严谨的concept:
template <typename T> concept AllocatorAware = requires { typename T::allocator_type; T{typename T::allocator_type{}}; // 验证类型支持分配器构造 };
2. 条件性构造函数实现
不能用void作为函数参数类型,正确的做法是通过concept或SFINAE选择性启用构造函数:
方案一:C++20 Concept约束构造函数
#include <iostream> #include <string_view> #include <memory> #include <string> template <typename T> concept AllocatorAware = requires { typename T::allocator_type; T{typename T::allocator_type{}}; }; template <typename StringType = std::string> struct URL { // 无参构造:所有类型通用 URL() : mystr{} {} // 带分配器构造:仅对AllocatorAware类型启用 URL(const typename StringType::allocator_type& alloc) requires AllocatorAware<StringType> : mystr{alloc} {} StringType mystr; }; int main() { URL myurl; // 正常,std::string无参构造 URL<std::string> myurl_alloc(std::allocator<char>{}); // 正常,带分配器构造 URL<std::string_view> myurl_view; // 正常,仅无参构造可用 // URL<std::string_view> myurl_view_alloc(std::allocator<char>{}); // 编译错误,符合预期 }
方案二:SFINAE兼容C++11及以上
如果需要兼容C++11/17,可以用std::enable_if实现:
#include <iostream> #include <string_view> #include <memory> #include <string> #include <type_traits> template <typename T, typename = void> struct is_allocator_aware : std::false_type {}; template <typename T> struct is_allocator_aware<T, std::void_t<typename T::allocator_type>> : std::true_type {}; template <typename StringType = std::string> struct URL { // 无参构造 URL() : mystr{} {} // 带分配器构造:仅当类型支持分配器时启用 template <typename Alloc = typename StringType::allocator_type> URL(const Alloc& alloc, typename std::enable_if<is_allocator_aware<StringType>::value>::type* = nullptr) : mystr{alloc} {} StringType mystr; };
3. 优雅性总结
- C++20的concept方案是最优雅的:代码简洁直观,编译时错误提示清晰,扩展性强(新增非分配器感知类型无需修改容器代码)。
- 避免了
std::conditional_t的提前实例化问题,同时通过构造函数的选择性启用,自然实现了"带/不带分配器"的分支逻辑,无需用void这种hack方式。
内容的提问来源于stack exchange,提问作者glades
相关产品推荐
相关产品推荐

