如何编写兼容可变与不可变gsl::span的只读访问模板函数?
解决gsl::span可变/不可变兼容的只读模板函数问题
你的问题核心在于模板推导阶段不会考虑隐式转换:当你传入gsl::span<int>给foo(gsl::span<const T>)时,编译器会尝试从参数推导T,直接推导出T=int,此时函数参数要求是gsl::span<const int>,但gsl::span<int>到gsl::span<const int>的隐式转换只会在推导完成后进行,导致推导失败,编译报错。
下面提供几种实用的解决方法,覆盖不同C++版本的需求:
方法1:重载函数(兼容C++11及以上)
最直观的方式是提供两个重载,分别接收可变和不可变的span,然后统一转发到只读逻辑的实现函数:
#include <gsl/gsl> // 核心只读实现 template <typename T> void foo_impl(gsl::span<const T> x) { // 这里只能进行只读操作,比如遍历元素 for (const auto& val : x) { // do something read-only } } // 接受可变span,自动转换为只读span template <typename T> void foo(gsl::span<T> x) { foo_impl(x); } // 直接接受不可变span template <typename T> void foo(gsl::span<const T> x) { foo_impl(x); } int main() { int arr[] = {0, 1, 2, 3, 4}; auto s1 = gsl::span<int>(arr); const auto s2 = gsl::span<const int>(arr); foo(s1); // 调用可变span重载 foo(s2); // 调用不可变span重载 }
这种方法兼容性最好,不需要依赖高版本C++特性,而且逻辑清晰,能明确区分两种输入类型。
方法2:使用非推导上下文(C++20及以上)
利用C++20新增的std::type_identity(或自定义identity结构体)让模板参数T处于非推导上下文,这样编译器会跳过参数推导,直接尝试隐式转换:
#include <gsl/gsl> #include <type_traits> template <typename T> void foo(gsl::span<const std::type_identity_t<T>> x) { // 只读操作逻辑 } int main() { int arr[] = {0, 1, 2, 3, 4}; auto s1 = gsl::span<int>(arr); const auto s2 = gsl::span<const int>(arr); foo(s1); // 隐式转换为span<const int>,T被推导为int foo(s2); // 直接匹配span<const int>,T被推导为int }
std::type_identity_t<T>是一个非推导上下文,编译器不会从传入的span<int>推导T,而是会寻找合适的T使得span<const T>能接收输入参数,自然会匹配T=int,此时隐式转换就能生效。
如果你需要兼容C++17及以下,可以自己实现一个简单的identity结构体:
template <typename T> struct identity { using type = T; }; template <typename T> using identity_t = typename identity<T>::type; // 然后把函数改成: template <typename T> void foo(gsl::span<const identity_t<T>> x) {}
方法3:接受任意Span类型并内部转换(兼容C++11及以上)
如果不想写重载,可以让模板接受任意类型的Span,然后在函数内部转换为只读版本:
#include <gsl/gsl> template <typename Span> void foo(Span x) { using ElementType = typename Span::element_type; gsl::span<const ElementType> read_only_x = x; // 后续所有操作都使用read_only_x,保证只读访问 for (const auto& val : read_only_x) { // read-only operation } } int main() { int arr[] = {0, 1, 2, 3, 4}; auto s1 = gsl::span<int>(arr); const auto s2 = gsl::span<const int>(arr); foo(s1); foo(s2); }
这种方法的优点是只需要一个模板函数,但要注意:如果不小心直接修改了参数x,还是会破坏只读约束,所以必须确保函数内部只使用转换后的read_only_x。
内容的提问来源于stack exchange,提问作者R zu
相关产品推荐
相关产品推荐

