我正在开发一个
constexpr
解析器,它解析文本文件并生成对象表示。
我正在使用
G++ 14.1.0
,但也可以切换(现在我感觉 G++ 比 CLang
支持更多最新的 C++)。
我经常使用
std::ranges::views::split
,然后通过 std::string_view
将值转换为 std::ranges::views::split
。
最小示例代码:
#include <ranges>
#include <string_view>
using namespace std::literals::string_view_literals;
int main() {
constexpr std::string_view input = "A,B B,C C,D C,E D,F"sv;
constexpr auto working =
input | std::ranges::views::split(","sv) |
std::ranges::views::transform(
[](const auto input) { return std::string_view(input); });
}
因为这是大量的复制和粘贴并使代码难以阅读,所以我尝试将转换构造函数的调用甚至整个转换移动到
constexpr
函数中。
我尝试的第一个是:
#include <ranges>
#include <string_view>
using namespace std::literals::string_view_literals;
int main() {
constexpr std::string_view input = "A,B B,C C,D C,E D,F"sv;
constexpr auto working =
input | std::ranges::views::split(","sv) |
std::ranges::views::transform(std::string_view::string_view);
}
,导致错误:
'string_view' is not a member of 'std::string_view' {aka 'std::basic_string_view<char>'}
和:
#include <ranges>
#include <string_view>
using namespace std::literals::string_view_literals;
int main() {
constexpr std::string_view input = "A,B B,C C,D C,E D,F"sv;
constexpr auto working =
input | std::ranges::views::split(","sv) |
std::ranges::views::transform(std::string_view);
}
,导致错误:
expected primary-expression before ')'
。
我尝试的第三个是:
#include <concepts>
#include <ranges>
#include <string_view>
using namespace std::literals::string_view_literals;
[[nodiscard]] static constexpr std::string_view to_string_view(const auto input)
requires(std::constructible_from<std::string_view, decltype(input)>)
{
return std::string_view(input);
}
int main() {
constexpr std::string_view input = "A,B B,C C,D C,E D,F"sv;
constexpr auto test = std::ranges::views::transform(
input | std::ranges::views::split(","sv), to_string_view);
}
,这会导致错误
> <source>: In function 'int main()':
> <source>:15:56: error: no match for call to '(const std::ranges::views::_Transform) (std::ranges::split_view<std::basic_string_view<char>, std::basic_string_view<char> >, <unresolved overloaded function type>)'
> 15 | constexpr auto test = std::ranges::views::transform(
> | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
> 16 | input | std::ranges::views::split(","sv), to_string_view);
> | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> In file included from <source>:2:
> /opt/compiler-explorer/gcc-trunk-20240629/include/c++/15.0.0/ranges:2212:9: note: candidate: 'template<class _Range, class _Fp> requires (viewable_range<_Range>) && (__can_transform_view<_Range, _Fp>) constexpr auto std::ranges::views::_Transform::operator()(_Range&&, _Fp&&) const'
> 2212 | operator() [[nodiscard]] (_Range&& __r, _Fp&& __f) const
> | ^~~~~~~~
> /opt/compiler-explorer/gcc-trunk-20240629/include/c++/15.0.0/ranges:2212:9: note: template argument deduction/substitution failed:
> <source>:15:56: note: couldn't deduce template parameter '_Fp'
> 15 | constexpr auto test = std::ranges::views::transform(
> | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
> 16 | input | std::ranges::views::split(","sv), to_string_view);
> | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> /opt/compiler-explorer/gcc-trunk-20240629/include/c++/15.0.0/ranges:991:9: note: candidate: 'template<class ... _Args> requires __adaptor_partial_app_viable<_Derived, _Args ...> constexpr auto std::ranges::views::__adaptor::_RangeAdaptor<_Derived>::operator()(_Args&& ...) const [with _Args = {_Args ...}; _Derived = std::ranges::views::_Transform]'
> 991 | operator()(_Args&&... __args) const
> | ^~~~~~~~
> /opt/compiler-explorer/gcc-trunk-20240629/include/c++/15.0.0/ranges:991:9: note: template argument deduction/substitution failed:
> /opt/compiler-explorer/gcc-trunk-20240629/include/c++/15.0.0/ranges:991:9: note: constraints not satisfied
> /opt/compiler-explorer/gcc-trunk-20240629/include/c++/15.0.0/ranges: In substitution of 'template<class ... _Args> requires __adaptor_partial_app_viable<_Derived, _Args ...> constexpr auto std::ranges::views::__adaptor::_RangeAdaptor<std::ranges::views::_Transform>::operator()(_Args&& ...) const [with _Args = std::ranges::views::_Transform]':
> <source>:15:56: required from here
> 15 | constexpr auto test = std::ranges::views::transform(
> | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
> 16 | input | std::ranges::views::split(","sv), to_string_view);
> | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> /opt/compiler-explorer/gcc-trunk-20240629/include/c++/15.0.0/ranges:921:13: required for the satisfaction of '__adaptor_partial_app_viable<_Derived, _Args ...>' [with _Derived = std::ranges::views::_Transform; _Args = {}]
> /opt/compiler-explorer/gcc-trunk-20240629/include/c++/15.0.0/ranges:922:28: note: the expression 'sizeof ... (_Args ...) == (_Adaptor::_S_arity) - 1 [with _Args = {}; _Adaptor = std::ranges::views::_Transform]' evaluated to 'false'
> 922 | && (sizeof...(_Args) == _Adaptor::_S_arity - 1)
> | ~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~
> Compiler returned: 1
我猜这里的问题是,
to_string_view
没有为特定类型实例化,因此编译器找不到它,但这是一个猜测。
所以我尝试直接尝试最终目标,并将整个转换转移到一个函数中:
#include <concepts>
#include <functional>
#include <ranges>
#include <string_view>
using namespace std::literals::string_view_literals;
static auto constexpr to_string_transform_view = std::bind_back(
std::ranges::views::transform, [](const auto input)
requires(std::constructible_from<std::string_view, decltype(input)>)
{ return std::string_view(input); });
int main() {
constexpr std::string_view input = "A,B B,C C,D C,E D,F"sv;
auto constexpr test =
to_string_transform_view(input | std::ranges::views::split(","sv));
}
需要帮助:可以编译,但我失去了很好的
|
语法,这更容易阅读。我怎样才能改进这一点,使 to_string_transform_view 的定义不会变得(太)难以阅读?
而且,好吧,现在只有一次,为了一件非常简单的事情而使用了这个“丑陋”的 lambda 函数。 因为我也需要这个来做其他事情,所以我更喜欢有更好的方法。
感谢 StackOverflow 中的各种答案,我看到
boost
提供了两个选项:
boost::value_factory
:#include <concepts>
#include <functional>
#include <ranges>
#include <string_view>
#include <boost/functional/value_factory.hpp>
using namespace std::literals::string_view_literals;
static auto constexpr to_string_transform_view = std::bind_back(
std::ranges::views::transform, boost::value_factory<std::string_view>());
int main() {
constexpr std::string_view input = "A,B B,C C,D C,E D,F"sv;
auto constexpr test =
to_string_transform_view(input | std::ranges::views::split(","sv));
}
boost::lambda::constructor
#include <concepts>
#include <functional>
#include <ranges>
#include <string_view>
#include <boost/lambda/construct.hpp>
using namespace std::literals::string_view_literals;
static auto constexpr to_string_transform_view = std::bind_back(
std::ranges::views::transform, boost::lambda::constructor<std::string_view>());
int main() {
constexpr std::string_view input = "A,B B,C C,D C,E D,F"sv;
auto constexpr test =
to_string_transform_view(input | std::ranges::views::split(","sv));
}
问题:
不幸的是,
ranges::to
要求C
必须not满足view
,所以我们目前不能像那样使用它
input | std::views::split(","sv)
| std::views::transform(ranges::to<std::string_view>());
但是,可以制作一个在类型之间转换的函数对象来传递给
views::transform
,例如
template<class To>
inline constexpr auto static_caster =
[]<class From>(From&& from) -> To
requires requires { static_cast<To>(std::declval<From>()); }
{ return static_cast<To>(std::forward<From>(from)); };
constexpr std::string_view input = "A,B B,C C,D C,E D,F"sv;
/* constexpr */ auto working =
input | std::views::split(","sv)
| std::views::transform(static_caster<std::string_view>);
请注意,
split_view
不是const
可迭代的,即它的begin()
不是const
限定的,因此它的const
对象不是range
。
您不需要将
std::bind_back
应用于范围适配器对象。使用单个参数调用 std::ranges::views::transform
相当于使用 std::bind_back
,并且还支持 |
语法。
只需删除
bind_back
即可正常工作。
#include <concepts>
#include <ranges>
#include <string_view>
using namespace std::literals::string_view_literals;
constexpr auto to_string_transform_view =
std::ranges::views::transform([](const auto input)
requires(std::constructible_from<std::string_view, decltype(input)>)
{ return std::string_view(input); });
int main() {
constexpr std::string_view input = "A,B B,C C,D C,E D,F"sv;
constexpr auto test =
input | std::ranges::views::split(","sv) | to_string_transform_view;
}