【问题标题】:boost::adaptors::transformed for classes without const begin/endboost::adaptors::transformed 用于没有 const begin/end 的类
【发布时间】:2020-11-18 10:08:28
【问题描述】:

我正在尝试将一个对象传递给boost::adaptors::transformed。但是,这似乎只有在该对象的类定义了beginendconst 版本时才有效。然而,这对我来说不是这样,因为迭代这个类的对象会修改对象本身的内部状态。这是一个使用虚拟类 Vector 的最小示例,它仅公开非 const 版本 beginend 或其 _v 成员,我可以更改什么来使其工作?

#include <initializer_list>
#include <vector>

#include <boost/range/adaptors.hpp>

template<typename T>
class Vector
{
public:
  using value_type = typename std::vector<T>::value_type;
  using reference = typename std::vector<T>::reference;
  using iterator = typename std::vector<T>::iterator;

  Vector(std::initializer_list<T> init)
  : _v(init)
  {}

  iterator begin() { return _v.begin(); }
  iterator end() { return _v.end(); }

private:
  std::vector<T> _v;
};

int main()
{
  Vector<int> v{1, 2, 3, 4};

  auto t = [](int i){ return 2 * i; };

  auto range(v | boost::adaptors::transformed(t)); // does not compile
}

【问题讨论】:

  • 如果你仍然想继续错误的设计(在迭代过程中修改集合,肯定会带来错误)让你向量 mutable std::vector&lt;T&gt; _v; 并添加 const 版本。无论如何,您为什么认为 boost 作者会增加限制以仅使用常量迭代器?强烈建议您不要在不复制数据的情况下在 C++ 中使用函数式样式。 2. 使用你的 Haskell 或其他任何东西。
  • 附言。您只需要:#include &lt;array&gt; #include &lt;iostream&gt; int main(int argc,const char **argv) { std::array&lt;int,4&gt; src = {1, 2, 3, 4}; std::array&lt;int,4&gt; dst; for(std::size_t i=0; i &lt; src.size(); i++) { dst[i] = src[i] &lt;&lt; 1; } return 0; }
  • @VictorGubin 你会认为 OP 知道如何做到这一点。也许您已经看到了我没有看到的 OP 之前提出的问题,但是没有理由假设仅仅对这个问题有很大的无知。
  • @sehe - 我不认为 OP 有任何无知。我认为他试图使用命令式编程语言作为函数式编程语言。它带来了真正的过度设计。更复杂的转换可以用同样的简单循环来完成。
  • P.S.我对范围概念的理解 - 改进诸如 find_if 之类的操作,其中 lambda 可用于按字段搜索容器中的结构。或将一个对象的向量转换为向量或另一个对象。恕我直言,使用 lambda 转换对象是一种设计变态。也许对于一些功能齐全的 PL 来说还可以,但不确定 C++ 是否还可以。实际上是一个疯狂的逻辑,代码在几个月内没有人可以阅读,包括作者在内,编译器无法优化。

标签: c++ boost boost-range


【解决方案1】:

一般来说,迭代修改集合是一种代码味道。

当然,某些东西可以是逻辑上的 const,这就是我们使用mutable 关键字的目的。我可以看到大致有两种方法

请记住,线程感知库可能会假定 constoperations 是线程安全保证(因此要么按位不可变,要么仅对成员互斥锁等同步原语进行操作)。

使容器存储可变

Live On Compiler Explorer

#include <initializer_list>
#include <vector>
#include <boost/range/adaptors.hpp>
#include <fmt/ranges.h>

using boost::adaptors::transformed;

template<typename T>
class Vector {
    using Cont = std::vector<T>;
  public:
    using value_type     = typename Cont::value_type;
    using reference      = typename Cont::reference;
    using iterator       = typename Cont::iterator;
    using const_iterator = typename Cont::const_iterator;

    Vector(std::initializer_list<T> init) : _v(init) {}

    iterator       begin()          { return _v.begin(); } 
    iterator       end()            { return _v.end();   } 
    const_iterator begin() const    { return _v.begin(); } 
    const_iterator end() const      { return _v.end();   } 
    //const_iterator cbegin() const { return _v.begin(); } 
    //const_iterator cend() const   { return _v.end();   } 
  private:
    Cont mutable _v;
};

static auto twice(int i) { return 2 * i; }

int main() {
    fmt::print("{} -> {}\n",
        Vector {1, 2, 3, 4},
        Vector {1, 2, 3, 4} | transformed(twice));
}

打印

{1, 2, 3, 4} -> {2, 4, 6, 8}

更纯粹的方法:可变元素数据

为了好玩,让我们创建一个元素来跟踪其值被观察到的次数:

Live On Compiler Explorer

#include <initializer_list>
#include <vector>
#include <boost/range/adaptors.hpp>
#include <fmt/ranges.h>

using boost::adaptors::transformed;

struct Element {
    Element(int value) : value(value) {}
    operator int() const { ++usage_counter; return value; }
    long usages() const { return usage_counter; }

  private:
    mutable long usage_counter = 0;
    int value;
};

template<typename T>
class Vector {
    using Cont = std::vector<T>;
  public:
    using value_type     = typename Cont::value_type;
    using reference      = typename Cont::reference;
    using iterator       = typename Cont::iterator;
    using const_iterator = typename Cont::const_iterator;

    Vector(std::initializer_list<T> init) : _v(init) {}

    iterator       begin()          { return _v.begin(); } 
    iterator       end()            { return _v.end();   } 
    const_iterator begin() const    { return _v.begin(); } 
    const_iterator end() const      { return _v.end();   } 
    //const_iterator cbegin() const { return _v.begin(); } 
    //const_iterator cend() const   { return _v.end();   } 
  private:
    Cont _v;
};

static auto twice(int i) { return 2 * i; }

int main() {
    Vector<Element> const v {1, 2, 3, 4}; // note const

    fmt::print("{} -> {} (usages {})\n",
        v,
        v | transformed(twice),
        v | transformed(std::mem_fn(&Element::usages))
    );
}

打印

{1, 2, 3, 4} -> {2, 4, 6, 8} (usages {3, 3, 3, 3})

【讨论】:

  • 嗯,是的,我猜 mutable 解决了这个问题。我同意这个问题本身很奇怪,我没有创作有问题的集合,也许将其相关状态移动到一个单独的对象中更有意义,每个迭代器都持有一个 shared_ptr 或其他东西。
猜你喜欢
  • 1970-01-01
  • 2014-12-16
  • 2014-10-15
  • 2020-09-19
  • 1970-01-01
  • 2021-03-21
  • 1970-01-01
  • 1970-01-01
  • 2016-02-08
相关资源
最近更新 更多