boost::adaptors::transformed for 类 没有 const begin/end

boost::adaptors::transformed for classes without const begin/end

我正在尝试将对象传递给 boost::adaptors::transformed。但是,这似乎只有在该对象的 class 定义了 beginendconst 版本时才有效。然而,对我来说情况并非如此,因为遍历 this class 的对象会修改对象本身的内部状态。这是一个使用虚拟 class 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
}

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

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

Keep in mind that threading-aware libraries might assume that constoperations are threadsafety guarantees (so either bit-wise immutable OR e.g. only operation on synchronization primitives like a member mutex).

使容器存储可变

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})