boost::adaptors::transformed,用于不带 const begin/end 的类

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

提问人:Peter 提问时间:11/18/2020 最后编辑:Peter 更新时间:11/19/2020 访问量:142

问:

我正在尝试将一个对象传递给 .但是,这似乎只有在该对象的类定义了 和 的版本时才有效。然而,对我来说并非如此,因为遍历此类的对象会修改对象本身的内部状态。这是一个使用虚拟类的最小示例,该虚拟类仅公开非 const 版本和/或其成员,我可以更改什么来使其工作?boost::adaptors::transformedconstbeginendVectorbeginend_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
}
C++ 加速 提升范围

评论

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

答:

2赞 sehe 11/19/2020 #1

我想说的是,一般来说,迭代修改集合是一种代码气味。

当然,某些东西在逻辑上是常量的,这就是我们的关键词。我可以看到大约两种方法mutable

请记住,线程感知库可能会假设操作是线程安全保证(因此,要么是按位不可变的,要么仅在同步基元(如成员互斥锁)上操作)。const

使容器存储可变

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}

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

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

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

评论

1赞 Peter 11/20/2020
呵呵,是的,我想 mutable 解决了这个问题。我同意问题本身很奇怪,我没有创作有问题的集合,也许将其相关状态移动到每个迭代器都拥有shared_ptr的单独对象中会更有意义。