如何将C++20组合Ranges视图存储为类成员?
C++20 Ranges视图作为类成员的链接性与类型命名问题
问题描述
尝试将C++20的组合Ranges视图存储为类成员,代码在单编译单元场景下可正常运行,但将结构体放入头文件被多编译单元包含时,GCC会触发-Werror=subobject-linkage警告,提示splitter::view具有内部链接。此外,这类组合视图的类型无法直接命名,手动编写类型极度繁琐,后续修改视图逻辑(如添加filter)时维护成本极高。
原代码示例:
#include <ranges> #include <string_view> #include <fmt/core.h> struct splitter { static constexpr auto split = [](std::string_view command) { return command | std::views::split(' ') | std::views::filter([](auto range) { return !std::ranges::empty(range); }) | std::views::transform([](auto range) { return std::string_view(range); }); }; using ViewType = decltype(split(std::declval<std::string_view>())); ViewType view; splitter(std::string_view str) : view(split(str)) { } }; int main() { std::string_view str = " test foo bar "; splitter sptl(str); for (auto s : sptl) { fmt::print("token {} with size {}", s, s.size()); } }
解决方案
1. 解决内部链接警告问题
警告根源是static constexpr lambda的类型具有内部链接属性,导致ViewType继承了内部链接,多编译单元包含头文件时,每个单元的ViewType会被视为不同类型,引发链接冲突。
修复方法:将lambda改为具有外部链接的实体,推荐以下两种方案:
方案1:使用inline static constexpr类成员
#include <ranges> #include <string_view> #include <fmt/core.h> struct splitter { // 用inline static constexpr替代static constexpr,赋予外部链接属性 inline static constexpr auto split = [](std::string_view command) { return command | std::views::split(' ') | std::views::filter([](auto range) { return !std::ranges::empty(range); }) | std::views::transform([](auto range) { return std::string_view(range); }); }; using ViewType = decltype(split(std::declval<std::string_view>())); ViewType view; splitter(std::string_view str) : view(split(str)) { } // 实现begin/end,让splitter本身可迭代 auto begin() const { return std::ranges::begin(view); } auto end() const { return std::ranges::end(view); } }; int main() { std::string_view str = " test foo bar "; splitter sptl(str); for (auto s : sptl) { fmt::print("token '{}' with size {}\n", s, s.size()); } }
方案2:全局inline constexpr变量
如果不想将lambda放在类内部,可以将其定义为全局inline constexpr变量,同样具有外部链接:
#include <ranges> #include <string_view> #include <fmt/core.h> // 全局inline constexpr变量,外部链接 inline constexpr auto split = [](std::string_view command) { return command | std::views::split(' ') | std::views::filter([](auto range) { return !std::ranges::empty(range); }) | std::views::transform([](auto range) { return std::string_view(range); }); }; struct splitter { using ViewType = decltype(split(std::declval<std::string_view>())); ViewType view; splitter(std::string_view str) : view(split(str)) { } auto begin() const { return std::ranges::begin(view); } auto end() const { return std::ranges::end(view); } }; int main() { std::string_view str = " test foo bar "; splitter sptl(str); for (auto s : sptl) { fmt::print("token '{}' with size {}\n", s, s.size()); } }
2. 简化视图类型命名
组合Ranges视图的类型是嵌套的模板实例,手动编写几乎不可行,最佳实践是用decltype推导类型:
using ViewType = decltype(split(std::declval<std::string_view>())):通过调用split的推导结果直接获取类型,后续修改split中的视图组合逻辑时,ViewType会自动更新,无需手动维护类型定义。- 也可以用
std::invoke_result_t<decltype(split), std::string_view>替代,效果完全一致,写法略有不同。
内容的提问来源于stack exchange,提问作者CsorvaGep
相关产品推荐
相关产品推荐

