いくつかの例がありますPython C++で模倣する必要があるコード。特定のソリューションは必要ありません(コルーチンベースのyieldソリューションなど、受け入れられる答えでもありますが) )、私は単に何らかの方法でセマンティクスを再現する必要があります。
これは基本的なシーケンスジェネレータであり、明らかにマテリアライズバージョンを格納するには大きすぎます。
def pair_sequence():
for i in range(2**32):
for j in range(2**32):
yield (i, j)
目標は、上記のシーケンスの2つのインスタンスを維持し、それらを半ロックステップでチャンクで反復することです。以下の例では、first_pass
はペアのシーケンスを使用してバッファを初期化し、second_pass
は同じ正確なシーケンスを再生成し、バッファを再度処理します。
def run():
seq1 = pair_sequence()
seq2 = pair_sequence()
buffer = [0] * 1000
first_pass(seq1, buffer)
second_pass(seq2, buffer)
... repeat ...
C++のソリューションで見つけることができる唯一のことは、C++コルーチンを使用してyield
を模倣することですが、これを行う方法に関する適切なリファレンスは見つかりませんでした。また、この問題の代替(一般的ではない)ソリューションにも興味があります。パス間でシーケンスのコピーを保持するのに十分なメモリバジェットがありません。
ジェネレーターは、C++の別の名前であるInput Iteratorsにあります。たとえば、std::cin
は、char
のジェネレーターを持つことに似ています。
ジェネレーターの機能を理解するだけです。
あなたの些細な例では、それは十分に簡単です。概念的に:
struct State { unsigned i, j; };
State make();
void next(State&);
bool isDone(State const&);
もちろん、これを適切なクラスとしてラップします。
class PairSequence:
// (implicit aliases)
public std::iterator<
std::input_iterator_tag,
std::pair<unsigned, unsigned>
>
{
// C++03
typedef void (PairSequence::*BoolLike)();
void non_comparable();
public:
// C++11 (explicit aliases)
using iterator_category = std::input_iterator_tag;
using value_type = std::pair<unsigned, unsigned>;
using reference = value_type const&;
using pointer = value_type const*;
using difference_type = ptrdiff_t;
// C++03 (explicit aliases)
typedef std::input_iterator_tag iterator_category;
typedef std::pair<unsigned, unsigned> value_type;
typedef value_type const& reference;
typedef value_type const* pointer;
typedef ptrdiff_t difference_type;
PairSequence(): done(false) {}
// C++11
explicit operator bool() const { return !done; }
// C++03
// Safe Bool idiom
operator BoolLike() const {
return done ? 0 : &PairSequence::non_comparable;
}
reference operator*() const { return ij; }
pointer operator->() const { return &ij; }
PairSequence& operator++() {
static unsigned const Max = std::numeric_limts<unsigned>::max();
assert(!done);
if (ij.second != Max) { ++ij.second; return *this; }
if (ij.first != Max) { ij.second = 0; ++ij.first; return *this; }
done = true;
return *this;
}
PairSequence operator++(int) {
PairSequence const tmp(*this);
++*this;
return tmp;
}
private:
bool done;
value_type ij;
};
うーん... C++はもう少し冗長です:)
C++にはイテレータがありますが、イテレータの実装は簡単ではありません。 イテレータの概念 を調べて、新しいイテレータクラスを慎重に設計して実装する必要があります。ありがたいことに、Boostには iterator_facade テンプレートがあり、イテレーターとイテレーター互換ジェネレーターの実装に役立ちます。
時々 スタックレスコルーチンを使用してイテレータを実装できます 。
追伸 この記事 も参照してください。これは、Christopher M. Kohlhoffによるswitch
ハックとOliver Kowalkeによる Boost.Coroutine の両方に言及しています。 Oliver Kowalkeの作品 フォローアップ on Boost.Coroutine by Giovanni P. Deretta.
追伸一種のジェネレーターを書くこともできると思います with lambdas :
std::function<int()> generator = []{
int i = 0;
return [=]() mutable {
return i < 10 ? i++ : -1;
};
}();
int ret = 0; while ((ret = generator()) != -1) std::cout << "generator: " << ret << std::endl;
またはファンクターを使って:
struct generator_t {
int i = 0;
int operator() () {
return i < 10 ? i++ : -1;
}
} generator;
int ret = 0; while ((ret = generator()) != -1) std::cout << "generator: " << ret << std::endl;
追伸 Mordor コルーチンで実装されたジェネレーターは次のとおりです。
#include <iostream>
using std::cout; using std::endl;
#include <mordor/coroutine.h>
using Mordor::Coroutine; using Mordor::Fiber;
void testMordor() {
Coroutine<int> coro ([](Coroutine<int>& self) {
int i = 0; while (i < 9) self.yield (i++);
});
for (int i = coro.call(); coro.state() != Fiber::TERM; i = coro.call()) cout << i << endl;
}
Boost.Coroutine2 は非常によくサポートしているため(まったく同じyield
問題を解決したかったので見つけました)、元の意図に一致するC++コードを投稿しています。
#include <stdint.h>
#include <iostream>
#include <memory>
#include <boost/coroutine2/all.hpp>
typedef boost::coroutines2::coroutine<std::pair<uint16_t, uint16_t>> coro_t;
void pair_sequence(coro_t::Push_type& yield)
{
uint16_t i = 0;
uint16_t j = 0;
for (;;) {
for (;;) {
yield(std::make_pair(i, j));
if (++j == 0)
break;
}
if (++i == 0)
break;
}
}
int main()
{
coro_t::pull_type seq(boost::coroutines2::fixedsize_stack(),
pair_sequence);
for (auto pair : seq) {
print_pair(pair);
}
//while (seq) {
// print_pair(seq.get());
// seq();
//}
}
この例では、pair_sequence
は追加の引数を取りません。必要な場合は、std::bind
またはラムダを使用して、Push_type
コンストラクターに渡されるときに(coro_t::pull_type
の)1つの引数のみをとる関数オブジェクトを生成する必要があります。
独自のイテレータの作成に関係するすべての答えは完全に間違っています。このような答えは、Pythonジェネレーター(言語の最大かつユニークな機能の1つ))のポイントを見逃しています。ジェネレーターに関する最も重要なことは、実行が中断したところから再開することです。代わりに、operator ++またはoperator *が新たに呼び出されたときに、正しい情報がの先頭にあるように、状態情報を手動で保存する必要があります次の関数呼び出し:これが、独自のC++イテレータを書くのが非常に面倒な理由です;一方、ジェネレータはエレガントで、読み書きが簡単です。
PythonネイティブC++のジェネレーターには良いアナログはないと思います。少なくともまだありません( yieldはC++ 17に到達する)という噂があります =)。サードパーティに頼ることで(例えばYongweiのBoost提案)、または独自のものをローリングすることで、似たようなものを得ることができます。
ネイティブC++で最も近いのはスレッドです。スレッドは、一時停止したローカル変数のセットを維持し、ジェネレーターと非常によく似た中断したところから実行を継続できますが、ジェネレーターオブジェクトとその呼び出し元の間の通信をサポートするには、少し追加のインフラストラクチャをロールする必要があります。例えば。
// Infrastructure
template <typename Element>
class Channel { ... };
// Application
using IntPair = std::pair<int, int>;
void yield_pairs(int end_i, int end_j, Channel<IntPair>* out) {
for (int i = 0; i < end_i; ++i) {
for (int j = 0; j < end_j; ++j) {
out->send(IntPair{i, j}); // "yield"
}
}
out->close();
}
void MyApp() {
Channel<IntPair> pairs;
std::thread generator(yield_pairs, 32, 32, &pairs);
for (IntPair pair : pairs) {
UsePair(pair);
}
generator.join();
}
ただし、このソリューションにはいくつかの欠点があります。
Visual Studio 2015では、おそらくstd :: experimentalのジェネレーターを確認する必要があります。例: https://blogs.msdn.Microsoft.com/vcblog/2014/11/12/resumable-functions-in-c/ =
まさにあなたが探しているものだと思います。これは実験的なMicrosoft VC機能です。
比較的少数の特定のジェネレーターに対してのみこれを行う必要がある場合は、それぞれをクラスとして実装できます。メンバーデータはPythonジェネレーター関数のローカル変数と同等です。ジェネレーターが次に生成するものを返すnext関数があり、内部状態を更新します。
これは基本的にPythonジェネレーターの実装方法と似ています。大きな違いは、ジェネレーター関数のバイトコードへのオフセットを「内部状態」の一部として覚えられることです。ジェネレーターは、yieldを含むループとして記述できますが、代わりに前の値から次の値を計算する必要がありますpair_sequence
の場合、それは非常に簡単です。
終了を示す何らかの方法も必要です。返すものが「ポインターのような」ものであり、NULLが有効な降伏可能な値ではない場合、NULLポインターを終了インジケーターとして使用できます。それ以外の場合は、帯域外信号が必要です。
このようなものは非常に似ています:
struct pair_sequence
{
typedef pair<unsigned int, unsigned int> result_type;
static const unsigned int limit = numeric_limits<unsigned int>::max()
pair_sequence() : i(0), j(0) {}
result_type operator()()
{
result_type r(i, j);
if(j < limit) j++;
else if(i < limit)
{
j = 0;
i++;
}
else throw out_of_range("end of iteration");
}
private:
unsigned int i;
unsigned int j;
}
Operator()の使用は、このジェネレーターで何をしたいのかという質問に過ぎません。たとえば、ストリームとしてビルドし、istream_iteratorに適合させることもできます。
range-v を使用:
#include <iostream>
#include <Tuple>
#include <range/v3/all.hpp>
using namespace std;
using namespace ranges;
auto generator = [x = view::iota(0) | view::take(3)] {
return view::cartesian_product(x, x);
};
int main () {
for (auto x : generator()) {
cout << get<0>(x) << ", " << get<1>(x) << endl;
}
return 0;
}
this :のようなもの
使用例:
using ull = unsigned long long;
auto main() -> int {
for (ull val : range_t<ull>(100)) {
std::cout << val << std::endl;
}
return 0;
}
0から99までの数字を印刷します
さて、今日はC++ 11での簡単なコレクションの実装も探していました。私が見つけたものはすべてpythonジェネレーター、またはC#のyield演算子...などのようなものから遠すぎるか、複雑すぎるので、私は失望しました。
目的は、必要な場合にのみアイテムを発行するコレクションを作成することです。
私はそれがこのようになりたかった:
auto emitter = on_range<int>(a, b).yield(
[](int i) {
/* do something with i */
return i * 2;
});
私はこの投稿を見つけました、私見のベストアンサーはboost.coroutine2についてでした Yongwei W によって。それは著者が望んだものに最も近いからです。
ブーストルーチンを学習する価値があります。そして、おそらく週末に行います。しかし、これまでのところ、非常に小さな実装を使用しています。それが誰かに役立つことを願っています。
以下は、使用例と実装です。
Example.cpp
#include <iostream>
#include "Generator.h"
int main() {
typedef std::pair<int, int> res_t;
auto emitter = Generator<res_t, int>::on_range(0, 3)
.yield([](int i) {
return std::make_pair(i, i * i);
});
for (auto kv : emitter) {
std::cout << kv.first << "^2 = " << kv.second << std::endl;
}
return 0;
}
Generator.h
template<typename ResTy, typename IndexTy>
struct yield_function{
typedef std::function<ResTy(IndexTy)> type;
};
template<typename ResTy, typename IndexTy>
class YieldConstIterator {
public:
typedef IndexTy index_t;
typedef ResTy res_t;
typedef typename yield_function<res_t, index_t>::type yield_function_t;
typedef YieldConstIterator<ResTy, IndexTy> mytype_t;
typedef ResTy value_type;
YieldConstIterator(index_t index, yield_function_t yieldFunction) :
mIndex(index),
mYieldFunction(yieldFunction) {}
mytype_t &operator++() {
++mIndex;
return *this;
}
const value_type operator*() const {
return mYieldFunction(mIndex);
}
bool operator!=(const mytype_t &r) const {
return mIndex != r.mIndex;
}
protected:
index_t mIndex;
yield_function_t mYieldFunction;
};
template<typename ResTy, typename IndexTy>
class YieldIterator : public YieldConstIterator<ResTy, IndexTy> {
public:
typedef YieldConstIterator<ResTy, IndexTy> parent_t;
typedef IndexTy index_t;
typedef ResTy res_t;
typedef typename yield_function<res_t, index_t>::type yield_function_t;
typedef ResTy value_type;
YieldIterator(index_t index, yield_function_t yieldFunction) :
parent_t(index, yieldFunction) {}
value_type operator*() {
return parent_t::mYieldFunction(parent_t::mIndex);
}
};
template<typename IndexTy>
struct Range {
public:
typedef IndexTy index_t;
typedef Range<IndexTy> mytype_t;
index_t begin;
index_t end;
};
template<typename ResTy, typename IndexTy>
class GeneratorCollection {
public:
typedef Range<IndexTy> range_t;
typedef IndexTy index_t;
typedef ResTy res_t;
typedef typename yield_function<res_t, index_t>::type yield_function_t;
typedef YieldIterator<ResTy, IndexTy> iterator;
typedef YieldConstIterator<ResTy, IndexTy> const_iterator;
GeneratorCollection(range_t range, const yield_function_t &yieldF) :
mRange(range),
mYieldFunction(yieldF) {}
iterator begin() {
return iterator(mRange.begin, mYieldFunction);
}
iterator end() {
return iterator(mRange.end, mYieldFunction);
}
const_iterator begin() const {
return const_iterator(mRange.begin, mYieldFunction);
}
const_iterator end() const {
return const_iterator(mRange.end, mYieldFunction);
}
private:
range_t mRange;
yield_function_t mYieldFunction;
};
template<typename ResTy, typename IndexTy>
class Generator {
public:
typedef IndexTy index_t;
typedef ResTy res_t;
typedef typename yield_function<res_t, index_t>::type yield_function_t;
typedef Generator<ResTy, IndexTy> mytype_t;
typedef Range<IndexTy> parent_t;
typedef GeneratorCollection<ResTy, IndexTy> finalized_emitter_t;
typedef Range<IndexTy> range_t;
protected:
Generator(range_t range) : mRange(range) {}
public:
static mytype_t on_range(index_t begin, index_t end) {
return mytype_t({ begin, end });
}
finalized_emitter_t yield(yield_function_t f) {
return finalized_emitter_t(mRange, f);
}
protected:
range_t mRange;
};