【问题标题】:Adding syntactic sugar to C++向 C++ 添加语法糖
【发布时间】:2022-02-08 23:47:50
【问题描述】:

编辑 2:

新编辑:看起来 C++20 有一个新的范围库,从功能的角度来看,它可以满足我的需求。如何在 C++17 或更早版本上完成类似的操作?另外,Kotlin 语法糖可能吗?主要是人的例子:

val adam = Person("Adam").apply { 
    age = 20 // same as this.age = 20 or adam.age = 20
    city = "London"
}

编辑 1:

我不知道我的问题是否那么清楚,所以我将使用 Rust 的 map 举一个例子。

这是在 rust 中制作地图的方式:

let newVector = myVector.iter().map(|x|, x * 2)

这就是它在 C++ 中的实现方式

std::string s("hello");
std::vector<std::size_t> ordinals;
std::transform(s.begin(), s.end(), std::back_inserter(ordinals),
     [](unsigned char c) -> std::size_t { return c; });

这要冗长得多。

我想添加一些语法糖来做这样的事情:

std::string s("hello");
auto ordinals = my_ns::map(s,[](unsigned char c) -> std::size_t { return c; });

my_ns::map 的实现可能是这样的(我敢肯定这行不通,只是为了说明如何实现)

template<typename T, U>
map(T t, std::function<U> f)
{
    std::vector<U> ordinals;
    std::transform(t.begin(), t.end(), std::back_inserter(ordinals),
     [](U c) -> f(c));
    return ordinals;
}

在这种情况下,ordinals 不需要是 std::vector&lt;std::size_t&gt; 类型,它可以是 map 类型,它具有 std::vector&lt;T&gt; 的转换。使用类型映射的原因是能够将 reduce 与映射等函数链接起来。

原始问题

注意:这仅适用于个人项目,我不打算在生产或大多数时间使用 C++ 时使用它。

我最近一直在使用很多 Kotlin,并且正在尝试使用 Rust,我喜欢它们的高级功能。这让我想知道,我可以创建一些语法糖来模拟其中的一些功能吗?

我试图模仿的一些东西(只是一个例子)

锈图:

let newVector = myVector.iter().map(|x|, x * 2)

Kotlin 让

val adam = Person("Adam").apply { 
    age = 20 // same as this.age = 20 or adam.age = 20
    city = "London"
}

val str = "Hello"
str.let {
   println("The string's length is ${it.length}")
}

我不打算使用完全相同的语法,但我想知道是否可以执行以下操作:

int[] arr = {1, 2, 3};
auto d_arr = map(arr, [](int x){return x*2}) // maybe return a map type to be able to use .reduce, etc

第一个 kotlin let 示例我不知道该怎么做,但第二个也可以使用 lambdas 解决。

我正在考虑使用新类型并创建到 std::vector 和数组的转换,这与 C# 的 LINQ 的工作原理类似。

感谢任何有关如何执行此操作的提示。

Ps:我知道 std::transform 和其他功能(我可能会在实现时使用它们),但我想要一个比提供的更简单的 API(而且我也认为这会很好个人项目)。

【问题讨论】:

  • 是的,如何执行map(arr, [](int x){return x*2}) 之类的操作,然后我可以连接对其他函数(如reduce 等)的调用。我基本上想知道一种将语法糖添加到cpp 的好方法,如果有的话是可能的。
  • 听起来您可能正在寻找 C++20 的 ranges。您的示例大致翻译为auto d_arr = arr | views::transform([](int x){ return x*2; });demo
  • 该死,我需要多学习一些C++20,乍一看这看起来很不错。我已经编辑了我的帖子,试图让问题更清楚。
  • 建议改进标题以更具体地与所询问的问题相关
  • “这就是它在 C++ 中的完成方式”似乎不正确:原始示例生成数组的转换副本(如果我理解正确的话),而您的 C++ 版本就地转换。

标签: c++


【解决方案1】:

您的示例可以很容易地更改为工作 C++ 代码:

template <typename T, typename Fun> auto map(T t, Fun f) {
  std::vector<typename T::value_type> ordinals;
  std::transform(t.begin(), t.end(), std::back_inserter(ordinals), f);
  return ordinals;
}

这既不是非常惯用的,也不是针对性能进行优化的,但它确实有效。 (预先调整向量的大小,而不是使用 std::back_inserter 并通过引用传递 t 以大幅提升性能。)

编辑:可以添加reduce如下:

template <typename T, typename R, typename Fun> auto reduce(T t, R init, Fun f) {
  return std::transform(t.begin(), t.end(), init, f);
}

然后将它们组合起来:reduce(map(x, [](auto a) {...}), 0, [](int&amp; a, auto b) {...})

明显的警告:这将非常缓慢。这些函数学习模板概念很有趣,但在实践中,范围或旧式 std::reduce/... 会快得多。

编辑 2: 如果你想使用map作为成员函数,只需做一个包装类:

template<class T>
struct Wrapper {
  T d;
  template <typename Fun> auto map(Fun f) {
    std::vector<typename T::value_type> ordinals;
    std::transform(d.begin(), d.end(), std::back_inserter(ordinals), f);
    return wrap(ordinals);
  }
  template <typename R, typename Fun> auto reduce(R init, Fun f) {
    return wrap(std::transform(d.begin(), d.end(), init, f));
  }
};
template<class T> Wrapper<T> wrap(const T& t) { return {t}; }
template<class T> Wrapper<T> wrap(T&& t) { return {t}; }

那你就可以wrap(my_vec).map(...).reduce(..., ...)

for 循环在这里会更快,因为你做了很多不必要的复制:你不必要地在 map 中创建一个新向量并复制数据很多次(std::back_inserter,pass-by-值)。

【讨论】:

  • 谢谢,看起来这对地图来说做得很好。如果我想扩展它然后将它传递到一个 reduce 中,比如新的 c++20 范围库,我需要做什么?创建某种形式的新数据类型,我可以从中调用 reduce?
  • 谢谢,我承认我需要一点时间来考虑你的 reduce 实现。如果我为转换结果创建了一个新类型,你认为我可以将其调整为像map(x, [](auto a) {...}).reduce(0, [int&amp; a, auto b] {...}) 这样的名称吗?关于速度(这对我来说并不是真正关心的问题,这主要是好奇)但是如果使用 for 循环实现这些,你知道它们是否会更快吗?
  • @Samuel 在编辑答案时回复了您。
  • 如果有什么我可以帮助您理解 reduce 实现的,请告诉我!
  • 谢谢,现在事情已经很清楚了!就在我接受答案之前,是否可以使用 kotlin apply 示例(同时设置许多属性)来完成类似的操作?
【解决方案2】:

我想知道是否可以这样做:

int[] arr = {1, 2, 3};
auto d_arr = map(arr, [](int x){return x*2})

在这个意义上的“map”在 C++ 标准库中被称为“transform”。一个工作示例:

auto double_ = [](auto x){ return x*2; }; // you may also use a normal function
auto doubled_view = std::views::all(arr)
                  | std::views::transform(double_);

能够将reduce等函数与map链接起来。

您可以缩小范围。不幸的是,C++20 标准范围没有附带 reduce 函数,因此必须使用范围的另一种实现,或者改用基于迭代器的函数。后者的例子:

auto sum = std::reduce(
     std::execution::unseq, // optional execution policy
     doubled_view.begin(),
     doubled_view.end(),
     0
     // you can use a functor to do something other than add
     );

转换为 std::vector

您还可以从范围创建容器:

std::vector doubled_vector(
    doubled_view.begin(), doubled_view.end());

请注意,为范围视图的中间阶段创建容器可能会产生不必要的开销。

【讨论】:

  • auto 在最后一行推断出什么?
  • @M.M 进入视图。
  • 好的。 OP 似乎希望最终得到数据的转换副本(尽管问题并不完全清楚)
  • @M.M 我知道 OP 想要减少他们映射的内容。
猜你喜欢
  • 2011-01-24
  • 1970-01-01
  • 2011-08-02
  • 2011-09-01
  • 2023-03-15
  • 2014-10-17
  • 2017-12-21
  • 2010-10-19
  • 2012-05-22
相关资源
最近更新 更多