如何选择正确的函数模板重载?libcxx fill_n优化及模板匹配问题
问题1:为libcxx的__fill_n添加手动重载以优化为memset
我正在使用LLVM项目的libcxx 16.0.0版本,__algorithm/fill_n.h中的__fill_n函数如下:
// fill_n isn't specialized for std::memset, because the compiler already optimizes the loop to a call to std::memset. template <class _OutputIterator, class _Size, class _Tp> inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_SINCE_CXX20 _OutputIterator __fill_n(_OutputIterator __first, _Size __n, const _Tp& __value) { for (; __n > 0; ++__first, (void) --__n) *__first = __value; return __first; }
我的场景中该函数未被编译器优化为std::memset,因此希望手动添加重载,针对**平凡可拷贝的_Tp和连续迭代器(包括std::array的迭代器)**实现__fill_n。
解决方案
要实现这个重载,需要利用类型特性判断迭代器是否为连续迭代器、类型是否平凡可拷贝,同时保证重载的匹配优先级高于原模板:
// 引入libcxx的类型特性工具 #include <__type_traits/is_trivially_copyable.h> #include <__iterator/iterator_traits.h> #include <__iterator/contiguous_iterator_tag.h> #include <cstring> // 针对连续迭代器+平凡可拷贝类型的重载 template <class _ContiguousIterator, class _Size, class _Tp> inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_SINCE_CXX20 typename std::enable_if< std::is_same<typename std::iterator_traits<_ContiguousIterator>::iterator_category, std::contiguous_iterator_tag>::value && std::is_trivially_copyable<_Tp>::value, _ContiguousIterator >::type __fill_n(_ContiguousIterator __first, _Size __n, const _Tp& __value) { if (__n > 0) { std::memset(std::addressof(*__first), static_cast<unsigned char>(__value), __n * sizeof(_Tp)); } return __first + __n; }
说明
- 用
std::iterator_traits获取迭代器的分类,判断是否为contiguous_iterator_tag(连续迭代器) - 用
std::is_trivially_copyable判断_Tp是否为平凡可拷贝类型 - 通过
std::enable_if实现SFINAE,只有满足条件时这个重载才会被纳入候选集 - 实现中直接调用
std::memset,注意__value需要转换为unsigned char(符合memset的参数要求),最后返回__first + __n(连续迭代器支持随机访问)
问题2:调整模板函数签名以匹配特定迭代器重载
给定代码:
template <typename T, int N> struct foo_t { struct iterator {}; }; template <typename T> int f(T) { return 1; } template <typename T, int N> int f(typename foo_t<T, N>::iterator) { return 2; } int main() { foo_t<int, 10>::iterator a; return f(a); // 当前调用第一个重载,希望调用第二个 }
需要修改f的签名,使得main中的f(a)选择第二个重载,兼容C11至C19。
解决方案
问题出在第二个重载的模板参数T和N属于非推导上下文,编译器无法从foo_t<int,10>::iterator推导出T和N,因此只会匹配第一个模板。可以通过以下两种方式解决:
方案1:为迭代器添加模板参数的关联信息
修改foo_t::iterator,让它携带T和N的类型信息,然后通过类型萃取推导:
template <typename T, int N> struct foo_t { // 让iterator携带T和N的信息 struct iterator { using value_type = T; static constexpr int size = N; }; }; // 第一个重载:通用版本 template <typename T> int f(T) { return 1; } // 第二个重载:通过迭代器的内部类型推导T和N template <typename T, int N> int f(typename foo_t<T, N>::iterator) { return 2; } // 禁用第一个重载对foo_iterator的匹配 template <typename Iter> int f(Iter, typename std::enable_if< std::is_same<Iter, typename foo_t<typename Iter::value_type, Iter::size>::iterator>::value, void* >::type = nullptr) = delete;
方案2:使用模板偏特化+辅助函数(更简洁)
利用辅助类推导迭代器所属的foo_t模板参数:
#include <type_traits> template <typename T, int N> struct foo_t { struct iterator {}; }; // 辅助模板:判断类型是否为foo_t<T,N>::iterator template <typename Iter> struct is_foo_iterator : std::false_type {}; template <typename T, int N> struct is_foo_iterator<typename foo_t<T, N>::iterator> : std::true_type { using value_type = T; static constexpr int size = N; }; // 第一个重载:仅当不是foo_iterator时匹配 template <typename T> typename std::enable_if<!is_foo_iterator<T>::value, int>::type f(T) { return 1; } // 第二个重载:专门匹配foo_iterator template <typename Iter> typename std::enable_if<is_foo_iterator<Iter>::value, int>::type f(Iter) { // 如需获取T和N,可使用is_foo_iterator<Iter>::value_type和size return 2; }
说明
- 方案1通过在迭代器中嵌入
value_type和size,让编译器能推导出T和N; - 方案2通过辅助模板
is_foo_iterator识别目标迭代器类型,再用enable_if控制重载的可见性,确保目标迭代器优先匹配第二个重载; - 两种方案都符合C11至C19的标准,无需依赖C++20特性。
内容的提问来源于stack exchange,提问作者Dorian
相关产品推荐
相关产品推荐

