テクニック:メタプログラミングのための即時呼び出し関数式

一般的な C++ のガイドラインは、使用時に変数を初期化し、変数を 07 にすることです。 しかし、一度初期化されると変数が変更されず、ループを含むように初期化が複雑になることがあります。その後、IIFE (即時に呼び出される関数式) を使用できます。変数は、値を計算するラムダによって初期化されます。次に、値を生成するためにすぐに呼び出されます。その後、変数は使用時に初期化され、17 にすることもできます。 .

私は最近メタプログラミング ライブラリに取り組んでおり、IIFE が型情報の計算という少し異なるコンテキストで役立つことがわかりました。

TL;DR:25 !

チャレンジ:32

このブログ投稿のために、与えられたコンテナがコンテナの値の型を計算する型特性を実装したいとしましょう。

template <typename Container>
using value_type_of = …; // TBD

value_type_of<std::vector<int>> i; // int
value_type_of<float[3]> x; // float

これは 47 の動作です 私が欲しいもの:

  • 型に 51 がある場合 メンバ (標準ライブラリ コンテナなど) の場合、それを返します。
  • 型が配列の場合、配列の要素型を返します。
  • それ以外の場合、型特性の形式が正しくありません。

すぐに観察できます:配列でも 64 でもないものを渡すと、型特性の形式が正しくないはずです。 77 のコンパイル時および (C++20 より前の) 構文の高価なチェックを行う必要はありません。 .配列を 1 つの方法で処理し、83 を使用できます。 他のすべての場合。タイプに 96 がない場合 、特性は自動的に不正な形式になります。

最初の試み

これは 104 の非常に単純な実装です :

template <typename Container>
struct value_type_of_impl // default, non-array
{
    using type = typename Container::value_type; 
};

template <typename T, std::size_t N>
struct value_type_of_impl<T[N]> // arrays
{
    using type = T;
};

template <typename Container>
using value_type_of = typename value_type_of_impl<Container>::type;

115 がないので 型については、配列と非配列を区別するための特殊化が必要です。また、型エイリアスを特殊化できないため、ヘルパー クラス テンプレートを導入する必要があります。

動作しますが、冗長です。もっと良い方法を試してみましょう。

2 回目の試行

129 はありませんが、 型には 137 があります (147 …).156かかります 169 に応じて、最初のタイプまたは 2 番目のタイプのいずれかを選択します。 .これを見てください、これが私たちが望んでいるものです!

template <typename Container>
using value_type_of =
  std::conditional_t<std::is_array_v<Container>, // if
                  std::remove_extent_t<Container>, // then
                  typename Container::value_type>; // else

178 を使用してコンテナが配列かどうかを確認しています (183 …).そうであれば、197 を使用しています。 (202 …) 要素の型を取得するには、それ以外の場合は 213 を使用します .

これは最初の試みよりも簡潔ですが、醜いです。

さらに重要なことに、それは機能しません!

222 と書くとどうなるか考えてみてください :

std::conditional_t<std::is_array_v<float[3]>, // true
                std::remove_extent_t<float[3]>, // float
                typename float[3]::value_type>; // error! 

234 の 2 番目の引数が 関係ありません、まだあります!そして 245 258 であるため、形式が正しくありません 配列に 262 がありません .

ですから、もっとうまくやる必要があります。

3 回目の試行

必要なのは、ある種の 272 です 284 のベース バージョン .そのようなことは可能ですが、最後にしましょう 実際の 296 を許可する IIFE を使用します :

template <typename Container>
using value_type_of = decltype([]{
      if constexpr (std::is_array_v<Container>)
          return std::remove_extent_t<Container>{};
      else
          return typename Container::value_type{};
  }());

IIFE の従来の使用例と同様に、値を取得するためにすぐに呼び出すラムダでエイリアスを初期化します。ただし、ここでは type が必要です 、ではありません 、したがって、全体を 305 で囲む必要があります .この構文上のノイズの利点は、言語の能力をフルに活用できることです。この場合は 311 です。 型特性を実装します。

を返す必要があることに注意してください。 言語規則が要求するものであるため、適切な型の.

最終的な解決策

ラムダは実際には実行されないため、戻り値の型を計算するためだけに存在するため、方法はあまり重要ではありません 返される値を取得しました。これが 324 です 次の目的で設計されました:値が実際には使用されず、その型だけが使用されるコンテキストで値を取得するため。;私たち自身が必要です:

template <typename T>
T type(); // no definition

template <typename Container>
using value_type_of = decltype([]{
      if constexpr (std::is_array_v<Container>)
          return type<std::remove_extent_t<Container>>();
      else
          return type<typename Container::value_type>();
  }());

ゴッドボルト

341 であることは問題ではありません には定義がありません - 戻り値の型だけが重要です。

結論

ラムダを使用して型を計算すると、間違いなく冗長性が低くなり、特殊化を使用する従来の TMP の方法よりもコードを明確にすることができます。欠点は、定義の周りの構文上のノイズです。ただし、慣れている場合は、しばらくすると間違いなくスキップされます。 C++ 関数は 355 を返すことができないため、型情報を返すのも少し冗長です。 (まだ).

364 でラムダを使用することを指摘しておく必要があります C++20 の機能です。古いバージョンをサポートする必要がある場合は、372 の通常の名前付き関数が必要です 戻り型:

template <typename Container>
auto value_type_of_()
{
    if constexpr (std::is_array_v<Container>)
        return type<std::remove_extent_t<Container>>();
    else
        return type<typename Container::value_type>();
}

template <typename Container>
using value_type_of = decltype(value_type_of_<Container>());

ゴッドボルト

それでも、私は特殊化を使用した実装よりもそれを好みます.