C++は(Haskellのように)遅延評価をネイティブにサポートしていません。
合理的な方法でC++で遅延評価を実装できるかどうか疑問に思っています。はいの場合、どのようにしますか?
編集:Konrad Rudolphの答えが好きです。
たとえば、matrix_addがmatrixで機能するようにTで基本的に機能するパラメータ化されたクラスlazyを使用するなど、より一般的な方法で実装できるかどうか疑問に思っています。
Tに対する操作は、代わりに遅延を返します。唯一の問題は、引数と操作コードを遅延自体の中に保存することです。誰もこれを改善する方法を見ることができますか?
妥当な方法でC++で遅延評価を実装できるかどうか疑問に思っています。はいの場合、どのようにしますか?
はい、これは可能であり、非常に頻繁に行われます。マトリックス計算用。これを容易にする主なメカニズムは、オペレーターのオーバーロードです。行列加算の場合を考えます。関数の署名は通常、次のようになります。
_matrix operator +(matrix const& a, matrix const& b);
_
さて、この関数を遅延させるには、実際の結果の代わりにプロキシを返すだけで十分です:
_struct matrix_add;
matrix_add operator +(matrix const& a, matrix const& b) {
return matrix_add(a, b);
}
_
ここで行う必要があるのは、このプロキシを記述することだけです。
_struct matrix_add {
matrix_add(matrix const& a, matrix const& b) : a(a), b(b) { }
operator matrix() const {
matrix result;
// Do the addition.
return result;
}
private:
matrix const& a, b;
};
_
魔法はメソッドoperator matrix()
にあります。これは_matrix_add
_からプレーンmatrix
への暗黙的な変換演算子です。このようにして、複数の操作を連鎖させることができます(もちろん適切なオーバーロードを提供することにより)。評価は、最終結果がmatrix
インスタンスに割り当てられた場合にのみ行われます。
[〜#〜] edit [〜#〜]もっと明確にすべきでした。現状では、評価は遅延して行われますが、同じ式で行われるため、コードは意味がありません。特に、_matrix_add
_構造が連鎖加算を許可するように変更されない限り、別の追加はこのコードを評価します。 C++ 0xは、可変長テンプレート(つまり、可変長のテンプレートリスト)を許可することにより、これを大幅に促進します。
ただし、このコードが実際に直接的なメリットをもたらす非常に単純なケースは次のとおりです。
_int value = (A + B)(2, 3);
_
ここで、A
とB
は2次元行列であり、逆参照はFortran表記法で行われる、つまり上記はone行列合計の要素。もちろん、行列全体を追加するのは無駄です。 _matrix_add
_救助に:
_struct matrix_add {
// … yadda, yadda, yadda …
int operator ()(unsigned int x, unsigned int y) {
// Calculate *just one* element:
return a(x, y) + b(x, y);
}
};
_
他の例もたくさんあります。少し前に関連するものを実装したことを思い出しました。基本的に、固定の事前定義されたインターフェイスに準拠する文字列クラスを実装する必要がありました。ただし、特定の文字列クラスは、実際にはメモリに保存されていない巨大な文字列を処理しました。通常、ユーザーは関数infix
を使用して、元の文字列から小さな部分文字列にアクセスするだけです。文字列型のこの関数をオーバーロードして、文字列への参照を保持するプロキシを、目的の開始位置と終了位置とともに返すようにしました。この部分文字列が実際に使用された場合にのみ、C APIを照会して文字列のこの部分を取得しました。
Boost.Lambdaは非常に素晴らしいですが、 Boost.Proto はexactly探しているものです。すでにallC++演算子のオーバーロードがあり、デフォルトでproto::eval()
が呼び出されたときに通常の機能を実行しますが、変更することもできます。
Konradが既に説明したことをさらに入れ子にして、すべて遅延実行される演算子の呼び出しをサポートできます。 Konradの例では、1つの操作の2つのオペランドに対して、2つの引数を正確に格納できる式オブジェクトがあります。問題は、one部分式のみを遅延して実行することです。これは、単純な用語で言えば遅延評価の概念をうまく説明しますが、パフォーマンスは大幅には向上しません。 。他の例では、operator()
を適用して、その式オブジェクトを使用して一部の要素のみを追加する方法も示しています。しかし、任意の複雑な式を評価するには、その構造もstoreできるメカニズムが必要です。そのためにテンプレートを回避することはできません。そしてその名前はexpression templates
。アイデアは、テンプレート化された1つの式オブジェクトが、操作がノードで、オペランドが子ノードであるツリーのように、任意のサブ式の構造を再帰的に格納できるということです。 very良い説明については、今日見つけたばかりです(以下のコードを書いた数日後) here を参照してください。
template<typename Lhs, typename Rhs>
struct AddOp {
Lhs const& lhs;
Rhs const& rhs;
AddOp(Lhs const& lhs, Rhs const& rhs):lhs(lhs), rhs(rhs) {
// empty body
}
Lhs const& get_lhs() const { return lhs; }
Rhs const& get_rhs() const { return rhs; }
};
単純なポイントタイプのoperator +の次の定義からわかるように、追加操作は、ネストされた操作も含めて保存されます。
struct Point { int x, y; };
// add expression template with point at the right
template<typename Lhs, typename Rhs> AddOp<AddOp<Lhs, Rhs>, Point>
operator+(AddOp<Lhs, Rhs> const& lhs, Point const& p) {
return AddOp<AddOp<Lhs, Rhs>, Point>(lhs, p);
}
// add expression template with point at the left
template<typename Lhs, typename Rhs> AddOp< Point, AddOp<Lhs, Rhs> >
operator+(Point const& p, AddOp<Lhs, Rhs> const& rhs) {
return AddOp< Point, AddOp<Lhs, Rhs> >(p, rhs);
}
// add two points, yield a expression template
AddOp< Point, Point >
operator+(Point const& lhs, Point const& rhs) {
return AddOp<Point, Point>(lhs, rhs);
}
今、あなたが持っている場合
Point p1 = { 1, 2 }, p2 = { 3, 4 }, p3 = { 5, 6 };
p1 + (p2 + p3); // returns AddOp< Point, AddOp<Point, Point> >
ここで、operator =をオーバーロードし、Pointタイプに適切なコンストラクターを追加して、AddOpを受け入れるだけです。定義を次のように変更します。
struct Point {
int x, y;
Point(int x = 0, int y = 0):x(x), y(y) { }
template<typename Lhs, typename Rhs>
Point(AddOp<Lhs, Rhs> const& op) {
x = op.get_x();
y = op.get_y();
}
template<typename Lhs, typename Rhs>
Point& operator=(AddOp<Lhs, Rhs> const& op) {
x = op.get_x();
y = op.get_y();
return *this;
}
int get_x() const { return x; }
int get_y() const { return y; }
};
そして、適切なget_xとget_yをメンバー関数としてAddOpに追加します。
int get_x() const {
return lhs.get_x() + rhs.get_x();
}
int get_y() const {
return lhs.get_y() + rhs.get_y();
}
Point型の一時ファイルを作成していないことに注意してください。それは多くのフィールドを持つ大きな行列であったかもしれません。しかし、結果が必要なときに、それを計算しますlazily。
Konradの投稿に追加するものは何もありませんが、実際のアプリで適切に実行される遅延評価の例については Eigen を参照してください。とてもpretty敬の念を起こさせます。
ヨハネスの答えは機能しますが、括弧の数が増えると、期待どおりに機能しません。以下に例を示します。
Point p1 = { 1, 2 }, p2 = { 3, 4 }, p3 = { 5, 6 }, p4 = { 7, 8 };
(p1 + p2) + (p3+p4)// it works ,but not lazy enough
3つのオーバーロードされた+演算子はケースをカバーしなかったため
AddOp<Llhs,Lrhs>+AddOp<Rlhs,Rrhs>
そのため、コンパイラーは(p1 + p2)または(p3 + p4)のいずれかをPointに変換する必要がありますが、これは十分に怠notではありません。なぜなら他のものより良いものはないからです。ここに私の拡張機能があります:オーバーロードされた演算子をさらに追加します+
template <typename LLhs, typename LRhs, typename RLhs, typename RRhs>
AddOp<AddOp<LLhs, LRhs>, AddOp<RLhs, RRhs>> operator+(const AddOp<LLhs, LRhs> & leftOperandconst, const AddOp<RLhs, RRhs> & rightOperand)
{
return AddOp<AddOp<LLhs, LRhs>, AddOp<RLhs, RRhs>>(leftOperandconst, rightOperand);
}
これで、コンパイラは上記のケースを正しく処理でき、暗黙的な変換は行われません。
_std::function
_を使用するテンプレートクラスの実装を考えています。クラスは、多かれ少なかれ、次のようになります。
_template <typename Value>
class Lazy
{
public:
Lazy(std::function<Value()> function) : _function(function), _evaluated(false) {}
Value &operator*() { Evaluate(); return _value; }
Value *operator->() { Evaluate(); return &_value; }
private:
void Evaluate()
{
if (!_evaluated)
{
_value = _function();
_evaluated = true;
}
}
std::function<Value()> _function;
Value _value;
bool _evaluated;
};
_
使用例:
_class Noisy
{
public:
Noisy(int i = 0) : _i(i)
{
std::cout << "Noisy(" << _i << ")" << std::endl;
}
Noisy(const Noisy &that) : _i(that._i)
{
std::cout << "Noisy(const Noisy &)" << std::endl;
}
~Noisy()
{
std::cout << "~Noisy(" << _i << ")" << std::endl;
}
void MakeNoise()
{
std::cout << "MakeNoise(" << _i << ")" << std::endl;
}
private:
int _i;
};
int main()
{
Lazy<Noisy> n = [] () { return Noisy(10); };
std::cout << "about to make noise" << std::endl;
n->MakeNoise();
(*n).MakeNoise();
auto &nn = *n;
nn.MakeNoise();
}
_
上記のコードは、コンソールに次のメッセージを生成するはずです。
_Noisy(0)
about to make noise
Noisy(10)
~Noisy(10)
MakeNoise(10)
MakeNoise(10)
MakeNoise(10)
~Noisy(10)
_
Noisy(10)
を出力するコンストラクターは、変数にアクセスするまで呼び出されないことに注意してください。
ただし、このクラスは完璧にはほど遠いです。最初のことは、Value
のデフォルトコンストラクターがメンバーの初期化時に呼び出される必要があることです(この場合はNoisy(0)
を出力します)。代わりに__value
_のポインタを使用できますが、パフォーマンスに影響するかどうかはわかりません。
何でも可能です。
それはあなたが何を意味するかに正確に依存します:
class X
{
public: static X& getObjectA()
{
static X instanceA;
return instanceA;
}
};
ここでは、最初の使用時に遅延評価されるグローバル変数の影響があります。
質問で新しく要求されたように。
Konrad Rudolphのデザインを盗んで拡張します。
遅延オブジェクト:
template<typename O,typename T1,typename T2>
struct Lazy
{
Lazy(T1 const& l,T2 const& r)
:lhs(l),rhs(r) {}
typedef typename O::Result Result;
operator Result() const
{
O op;
return op(lhs,rhs);
}
private:
T1 const& lhs;
T2 const& rhs;
};
どうやって使うのですか:
namespace M
{
class Matrix
{
};
struct MatrixAdd
{
typedef Matrix Result;
Result operator()(Matrix const& lhs,Matrix const& rhs) const
{
Result r;
return r;
}
};
struct MatrixSub
{
typedef Matrix Result;
Result operator()(Matrix const& lhs,Matrix const& rhs) const
{
Result r;
return r;
}
};
template<typename T1,typename T2>
Lazy<MatrixAdd,T1,T2> operator+(T1 const& lhs,T2 const& rhs)
{
return Lazy<MatrixAdd,T1,T2>(lhs,rhs);
}
template<typename T1,typename T2>
Lazy<MatrixSub,T1,T2> operator-(T1 const& lhs,T2 const& rhs)
{
return Lazy<MatrixSub,T1,T2>(lhs,rhs);
}
}
C++ 0xはすてきですべてです。両方とも、C++に大量の関数型プログラミングをもたらすことを目的としています。
C++ 0x で、ラムダ式によって行われます。
Haskellをインスピレーションとして取り上げましょう-コアに対して怠け者です。また、C#のLinqがモナド(列挙型)で列挙子をどのように使用するかを覚えておいてください。最後に、コルーチンがプログラマーに提供することになっているものを覚えておいてください。つまり、計算ステップ(生産者と消費者など)の相互分離です。そして、コルーチンが遅延評価にどのように関係しているかについて考えてみましょう。
上記のすべては何らかの形で関連しているようです。
次に、「怠lazな」ものの個人的な定義を抽出してみましょう。
解釈の1つは、計算を構成可能な方法で記述し、を実行する前にすることです。完全なソリューションを構成するために使用するこれらのパーツの一部は、巨大な(場合によっては無限の)データソースを非常によく利用し、完全な計算でも有限または無限の結果を生成します。
具体的にいくつかのコードを見てみましょう。そのための例が必要です!ここでは、例としてfizzbuzzの「問題」を選択しますが、これにはニースの怠zyな解決策があるからです。
Haskellでは、次のようになります。
_module FizzBuzz
( fb
)
where
fb n =
fmap merge fizzBuzzAndNumbers
where
fizz = cycle ["","","fizz"]
buzz = cycle ["","","","","buzz"]
fizzBuzz = zipWith (++) fizz buzz
fizzBuzzAndNumbers = Zip [1..n] fizzBuzz
merge (x,s) = if length s == 0 then show x else s
_
Haskell関数cycle
は、有限リスト内の値を永久に繰り返すだけで、有限リストから無限リスト(もちろん遅延)を作成します。熱心なプログラミングスタイルでは、そのようなものを書くと警告音が鳴ります(メモリオーバーフロー、無限ループ!)。しかし、怠zyな言語ではそうではありません。秘Theは、遅延リストはすぐには計算されないということです。たぶん決して。通常、後続のコードで必要なだけです。
上記のwhere
ブロックの3行目は、別の遅延を作成します!! list、無限のリストfizz
とbuzz
を、単一の2要素レシピ「いずれかの入力リストの文字列要素を単一の文字列に連結する」を使用して結合する。繰り返しますが、これをすぐに評価する場合は、コンピューターのリソースがなくなるまで待つ必要があります。
4行目では、有限レイジーリストfizzbuzz
を使用して、有限レイジーリスト_[1..n]
_のメンバーのタプルを作成します。結果はまだ怠laです。
fb
関数の本体であっても、熱心になる必要はありません。関数全体が解を含むリストを返しますが、それ自体もまた怠laです。 _fb 50
_の結果は、後で(部分的に)評価できる計算と考えることもできます。または、他のものと組み合わせて、さらに大きな(怠yな)評価を導きます。
そのため、「fizzbuzz」のC++バージョンを開始するには、計算の部分的なステップを、必要に応じて前のステップからのデータを描画するより大きな計算ビットに結合する方法を考える必要があります。
詳細は 私の要点 で確認できます。
コードの背後にある基本的なアイデアは次のとおりです。
C#とLinqから借用して、ステートフルな汎用型Enumerator
を「発明」します。
-部分計算の現在の値
-部分的な計算の状態(したがって、後続の値を生成できます)
-次の状態、次の値、さらにデータがあるか、列挙が終了したかを示すブール値を生成するワーカー関数。
_Enumerator<T,S>
_(ドット)の力を使用して_.
_インスタンスを構成できるように、このクラスには、Functor
やApplicative
などのHaskell型クラスから借用した関数も含まれています。
列挙子のワーカー関数は、常に_S -> std::Tuple<bool,S,T
_の形式です。ここでS
は状態を表すジェネリック型変数であり、T
は値-計算ステップの結果を表すジェネリック型変数です。
これはすべて、Enumerator
クラス定義の最初の行ですでに表示されています。
_template <class T, class S>
class Enumerator
{
public:
typedef typename S State_t;
typedef typename T Value_t;
typedef std::function<
std::Tuple<bool, State_t, Value_t>
(const State_t&
)
> Worker_t;
Enumerator(Worker_t worker, State_t s0)
: m_worker(worker)
, m_state(s0)
, m_value{}
{
}
// ...
};
_
したがって、特定の列挙子インスタンスを作成するために必要なことは、ワーカー関数を作成し、初期状態を保持し、これら2つの引数を使用してEnumerator
のインスタンスを作成する必要があります。
ここでの例-function range(first,last)
は、有限範囲の値を作成します。これは、Haskellの世界の遅延リストに対応しています。
_template <class T>
Enumerator<T, T> range(const T& first, const T& last)
{
auto finiteRange =
[first, last](const T& state)
{
T v = state;
T s1 = (state < last) ? (state + 1) : state;
bool active = state != s1;
return std::make_Tuple(active, s1, v);
};
return Enumerator<T,T>(finiteRange, first);
}
_
そして、たとえば次のように、この関数を使用できます。auto r1 = range(size_t{1},10);
-10個の要素を持つ怠elementsなリストを作成しました!
今、私たちの「すごい」体験にはすべてが欠けています。列挙子を作成する方法を確認することです。 Haskellsのcycle
関数に戻ると、これは一種のクールです。 C++の世界ではどのように見えるでしょうか?ここにあります:
_template <class T, class S>
auto
cycle
( Enumerator<T, S> values
) -> Enumerator<T, S>
{
auto eternally =
[values](const S& state) -> std::Tuple<bool, S, T>
{
auto[active, s1, v] = values.step(state);
if (active)
{
return std::make_Tuple(active, s1, v);
}
else
{
return std::make_Tuple(true, values.state(), v);
}
};
return Enumerator<T, S>(eternally, values.state());
}
_
入力として列挙子を取り、列挙子を返します。ローカル(ラムダ)関数eternally
は、値がなくなって無効になると入力列挙を開始値にリセットします-引数として指定したリストの無限の繰り返しバージョンがあります:: auto foo = cycle(range(size_t{1},3));
そして怠zyな「計算」をすでに恥知らずに作成できます。
Zip
は良い例で、2つの入力列挙子から新しい列挙子を作成することもできます。結果の列挙子は、入力列挙子のいずれか小さい方と同じ数の値を生成します(各入力列挙子に1つずつ、2つの要素を持つタプル)。 Zip
を_class Enumerator
_自体の中に実装しました。これがどのように見えるかです:
_// member function of class Enumerator<S,T>
template <class T1, class S1>
auto
Zip
( Enumerator<T1, S1> other
) -> Enumerator<std::Tuple<T, T1>, std::Tuple<S, S1> >
{
auto worker0 = this->m_worker;
auto worker1 = other.worker();
auto combine =
[worker0,worker1](std::Tuple<S, S1> state) ->
std::Tuple<bool, std::Tuple<S, S1>, std::Tuple<T, T1> >
{
auto[s0, s1] = state;
auto[active0, newS0, v0] = worker0(s0);
auto[active1, newS1, v1] = worker1(s1);
return std::make_Tuple
( active0 && active1
, std::make_Tuple(newS0, newS1)
, std::make_Tuple(v0, v1)
);
};
return Enumerator<std::Tuple<T, T1>, std::Tuple<S, S1> >
( combine
, std::make_Tuple(m_state, other.state())
);
}
_
「結合」は、両方のソースの状態と両方のソースの値を結合する結果となることに注意してください。
この投稿はすでにTL; DR;多くの場合、ここで...
概要
はい、遅延評価はC++で実装できます。ここでは、haskellから関数名を、C#列挙子とLinqからパラダイムを借用することでそれを行いました。 pythons itertools、btwに類似点があるかもしれません。彼らは同様のアプローチに従ったと思います。
私の実装(上記のGistリンクを参照)は単なるプロトタイプであり、実動コードではありません。したがって、私の側からのいかなる保証もありません。ただし、一般的な考え方を理解するためのデモコードとしても役立ちます。
そして、この答えは、最終的なC++バージョンのfizzbuzがないとどうなりますか?ここにあります:
_std::string fizzbuzz(size_t n)
{
typedef std::vector<std::string> SVec;
// merge (x,s) = if length s == 0 then show x else s
auto merge =
[](const std::Tuple<size_t, std::string> & value)
-> std::string
{
auto[x, s] = value;
if (s.length() > 0) return s;
else return std::to_string(x);
};
SVec fizzes{ "","","fizz" };
SVec buzzes{ "","","","","buzz" };
return
range(size_t{ 1 }, n)
.Zip
( cycle(iterRange(fizzes.cbegin(), fizzes.cend()))
.zipWith
( std::function(concatStrings)
, cycle(iterRange(buzzes.cbegin(), buzzes.cend()))
)
)
.map<std::string>(merge)
.statefulFold<std::ostringstream&>
(
[](std::ostringstream& oss, const std::string& s)
{
if (0 == oss.tellp())
{
oss << s;
}
else
{
oss << "," << s;
}
}
, std::ostringstream()
)
.str();
}
_
そして...さらにポイントを家に戻すために-ここでは、呼び出し元に「無限リスト」を返すfizzbuzzのバリエーションがあります。
_typedef std::vector<std::string> SVec;
static const SVec fizzes{ "","","fizz" };
static const SVec buzzes{ "","","","","buzz" };
auto fizzbuzzInfinite() -> decltype(auto)
{
// merge (x,s) = if length s == 0 then show x else s
auto merge =
[](const std::Tuple<size_t, std::string> & value)
-> std::string
{
auto[x, s] = value;
if (s.length() > 0) return s;
else return std::to_string(x);
};
auto result =
range(size_t{ 1 })
.Zip
(cycle(iterRange(fizzes.cbegin(), fizzes.cend()))
.zipWith
(std::function(concatStrings)
, cycle(iterRange(buzzes.cbegin(), buzzes.cend()))
)
)
.map<std::string>(merge)
;
return result;
}
_
(関数の実装、つまりコードが列挙子をどのように結合するかに依存するため)その関数の正確な戻り値の型が何であるかという質問を避ける方法を学ぶことができるので、示す価値があります。
また、ベクトルfizzes
およびbuzzes
を関数のスコープ外に移動する必要があったことを示しているため、最終的に外部にあるときに、遅延メカニズムが値を生成します。もしそうしていなかったら、iterRange(..)
コードは、なくなったベクターへのイテレーターを保存していたでしょう。
C++ 11では、stap :: shared_futureを使用してhiapayの答えと同様の遅延評価を実現できます。計算をラムダでカプセル化する必要がありますが、メモ化は次のように処理されます。
std::shared_future<int> a = std::async(std::launch::deferred, [](){ return 1+1; });
完全な例を次に示します。
#include <iostream>
#include <future>
#define LAZY(EXPR, ...) std::async(std::launch::deferred, [__VA_ARGS__](){ std::cout << "evaluating "#EXPR << std::endl; return EXPR; })
int main() {
std::shared_future<int> f1 = LAZY(8);
std::shared_future<int> f2 = LAZY(2);
std::shared_future<int> f3 = LAZY(f1.get() * f2.get(), f1, f2);
std::cout << "f3 = " << f3.get() << std::endl;
std::cout << "f2 = " << f2.get() << std::endl;
std::cout << "f1 = " << f1.get() << std::endl;
return 0;
}
遅延評価の非常に単純な定義(値は必要になるまで評価されない)を使用すると、ポインターとマクロ(構文シュガー用)を使用してこれを実装できると思います。
#include <stdatomic.h>
#define lazy(var_type) lazy_ ## var_type
#define def_lazy_type( var_type ) \
typedef _Atomic var_type _atomic_ ## var_type; \
typedef _atomic_ ## var_type * lazy(var_type); //pointer to atomic type
#define def_lazy_variable(var_type, var_name ) \
_atomic_ ## var_type _ ## var_name; \
lazy_ ## var_type var_name = & _ ## var_name;
#define assign_lazy( var_name, val ) atomic_store( & _ ## var_name, val )
#define eval_lazy(var_name) atomic_load( &(*var_name) )
#include <stdio.h>
def_lazy_type(int)
void print_power2 ( lazy(int) i )
{
printf( "%d\n", eval_lazy(i) * eval_lazy(i) );
}
typedef struct {
int a;
} simple;
def_lazy_type(simple)
void print_simple ( lazy(simple) s )
{
simple temp = eval_lazy(s);
printf("%d\n", temp.a );
}
#define def_lazy_array1( var_type, nElements, var_name ) \
_atomic_ ## var_type _ ## var_name [ nElements ]; \
lazy(var_type) var_name = _ ## var_name;
int main ( )
{
//declarations
def_lazy_variable( int, X )
def_lazy_variable( simple, Y)
def_lazy_array1(int,10,Z)
simple new_simple;
//first the lazy int
assign_lazy(X,111);
print_power2(X);
//second the lazy struct
new_simple.a = 555;
assign_lazy(Y,new_simple);
print_simple ( Y );
//third the array of lazy ints
for(int i=0; i < 10; i++)
{
assign_lazy( Z[i], i );
}
for(int i=0; i < 10; i++)
{
int r = eval_lazy( &Z[i] ); //must pass with &
printf("%d\n", r );
}
return 0;
}
関数print_power2
というマクロがありますeval_lazy
は、実際に必要になる直前に値を取得するために、ポインターを逆参照するだけです。遅延型はアトミックにアクセスされるため、完全にスレッドセーフです。