C++ で自動パラメーターを自動的に使用しない

C++14 の登場以来、08 を取るラムダ式を作成できます。 そして C++20 では、通常の関数に対しても同じことができます。この機能の出現により、すべてのパラメーターが 15 であるプログラミング スタイル しかし、この機能は常に最適であるとは限らず、率直に言って、必要以上に頻繁に使用するべきではないと思います.一般に、パラメータの型をより具体的にするほど、

人々がそれを好む理由

そのほうが簡単ですよね!誰もが明示的なパラメーターの型を書き出したがるわけではありません.

これは、テンプレートを多用するジェネリック プログラミングを行う場合の正当な言い訳になる可能性がありますが、多くの場合、「書くのが面倒」なタイプは、リファクタリングを行うことで回避できます。そうすることで、コードの品質が向上することさえあります。

たとえば、次のコードはオンラインで見つけたコードの修正版です。 25 の明示的な型について書き出すのは面倒だと思います。 :

std::vector<std::pair<double, double>> pairs;

return std::accumulate(
  pairs.cbegin(), pairs.cend(), 0,
  [](auto acc, const auto& pair) {
      return acc + pair.first * pair.second;
});

ペアの 32 には意味がないので、このスニペットを読んでも、このコードの意味についてはわかりません。 そして 40

55 の要素を変更するとどうなるでしょうか 名前付き構造に?

struct Outcome {
  double probability = 0;
  double value = 0;
};

std::vector<Outcome> distribution;

return std::accumulate(
  distribution.cbegin(), distribution.cend(), 0,
  [](double acc, const Outcome& outcome) {
      return acc + outcome.probability * outcome.value;
});

突然、このコードが離散確率変数の期待値を計算しようとしていることが明らかになりました!

残念ながら、コードにより良い型を与える代わりに、69 に順応してしまう人もいます。 71 を記述している場所でさえ、どこでも使用し始めるパラメータースタイル キーストロークをあまり節約しない、またはまったく節約しない:

const std::vector<int> v1 = ...;
const std::vector<int> v2 = ...;
std::vector<int> smaller_ones;

std::ranges::transform(v1, v2, std::back_inserter(smaller_ones),
  [](auto x, auto y) { return std::min(x, y); });

自動パラメータ生成テンプレート

ML や Rust などの一部のプログラミング言語では、型システムは定義に基づいて関数またはラムダ式の正確な型を推測できます。これらの言語には異なる型注釈構文もあり、パラメーターの型注釈はオプションになります。これらの言語でパラメーター型のないラムダ式は人間工学的で慣用的です。ユーザーがこれらの言語に慣れると、同じコーディング スタイルで C++ に戻ることがよくあります。

ただし、C++ では、これらの言語とは異なり、パラメーター型の「型推論」は幻想に過ぎません。テンプレート、オーバーロード、ADL (引数依存ルックアップ) はすべて、そのような型推論を不可能にします。 パラメーターは、制約のないテンプレートになります。たとえば、驚くべき cppinsights Web サイトを使用して、91 を確認できます。 次へ:

class __lambda_5_2
  {
    public:
    template<class type_parameter_0_0, class type_parameter_0_1>
    inline /*constexpr */ auto operator()(type_parameter_0_0 x, type_parameter_0_1 y) const
    {
      return (x * y) + 42;
    }
    private:
    template<class type_parameter_0_0, class type_parameter_0_1>
    static inline auto __invoke(type_parameter_0_0 x, type_parameter_0_1 y)
    {
      return (x * y) + 42;
    }

  } __lambda_5_2{};

問題は、テンプレート プログラミングが「通常の」プログラミングと同じ経験を持たないことです。そのため、コンパイラは多くの場合、型エラーを必要以上に遅くキャッチし、テンプレート コンテキストでの IDE オートコンプリート/エラー検出のサポートが悪化します。この問題は次のようになります。 104 を使用すると、一行以上のラムダ式を書き始めるとさらに顕著になります C++20 の通常の関数のパラメーター。

制約のないテンプレートは危険です

テンプレートが必要な場合でも、それらを制約することは、それらを使用するためのより良いアイデアです.Bjarne Stroustrup は彼の講演の中で、111 について考えるべきだと述べました. 概念として — 制約が最も少ないもの.1

テンプレートが制約されていない場合、誤ってインターフェイスに一致する型を持つことは簡単です。たとえば、3 次元のベクトル構造があり、それらに対して内積を実行したいと思うのは自然なことだとしましょう:

struct Vec3 {
  float x = 0;
  float y = 0;
  float z = 0;
};

auto dot(auto v1, auto v2) {
  return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
}

後で別の 4 次元ベクトルを追加する場合は、同じバージョンの 122 を呼び出すことができます。 これは 3 次元ベクトル用に準備されており、まったく予期しない結果が得られます:

struct Vec4 {
  float x = 0;
  float y = 0;
  float z = 0;
  float w = 0;
};

dot(Vec4{1, 2, 3, 4}, Vec4{1, 2, 3, 4}); // expects 30, gets 14

C++ コア ガイドラインでは、特に ADL と組み合わせた場合に、非常に目に見える範囲で制約のないテンプレートを使用する危険性についても言及されています。 2

明示的な型注釈はドキュメントの値を提供します

C++ 固有の問題がない言語でも、明示的なパラメーター型はドキュメント化の目的を提供し、リファクタリング中に「型チェックの障壁」として機能します。これが、ML の方言と Haskell で、明示的な型注釈のないトップレベル関数が悪いスタイルと見なされる理由であり、Rust はそうします。許すな!

静的に型付けされた言語でなじみのない API を使用する場合、型注釈はおそらく特定の関数呼び出しが何をするかの最初のヒントです。 パラメータの性質について、他の人や未来の自分に何のヒントも与えません。

結論

145 を常に回避できるとは限りません ただし、それらを使用する理由が単に便利である場合は特に、それらを避けることを検討する必要があります。

C++20 より前は、ラムダ式に概念や明示的なテンプレート アノテーションを使用する方法がありませんでした。また、場合によっては、154 を使用することで利便性と生産性が向上します パラメータはおそらくその欠点を上回ります.しかし、自動パラメータをコードの臭いと考えるのに十分なほどマイナス面があると思います.自動パラメータを使用するコードに会うときは、常に「ここで具象型を使用することは可能ですか?」と尋ねる必要があります.そうでない場合、次の質問は「ここで概念を使用することは可能ですか?」です。

<オール>
  • CppCon 2018:Bjarne Stroustrup 「概念:ジェネリック プログラミングの未来 (未来はここにあります)」↩
  • T.47:一般的な名前を持つ、目立つ制約のないテンプレートを避ける↩