C++ のいくつかの実験的機能

この記事では、C++23 で見たいと思っていて、Compiler Explorer に展開したいくつかの言語機能を紹介します。

これらの機能はワーキング ドラフトの一部ではなく、まだ C++ 委員会に提出されていないため、23 に実装されるかどうかについてコメントすることはできません!

自動非静的データ メンバー初期化子

しばらく前に、非静的データ メンバーの自動イニシャライザを紹介しました。当時は、clang 7 フォークに基づいていました。これはまだ将来の C++ バージョンで見たい機能であるため、Clang 11 の上にリベースしました。 、モノレポへの LLVM の移行のため、これは少し複雑でした (しかし、彼らがその移行を行ったことを非常に嬉しく思います!)。

こちらのコンパイラ エクスプローラで試すことができます:


#include <vector>
struct s {
 auto v1 = std::vector{3, 1, 4, 1, 5};
 std::vector<int> v2 = std::vector{3, 1, 4, 1, 5};
};

この機能に関する提案はまだありません。人々にこの機能に取り組むよう説得したいと思っています!

多次元添字演算子

アイデアは非常に単純です:添字式で複数の引数を許可することです:

struct image {
 pixel operator[](size_t x, size_t y) const;
};
/*...*/
pixel x = my_image[42, 42];

C++20 では、01 を廃止しました。 添字式:警告は GCC と Clang で既に実装されています。MSVC は驚くべき構文について警告しますが、非推奨についてはまだ言及していません。

int main() {
 int array[2] = {3, 4};
 //warning: top-level comma expression in array subscript is deprecated
 //(equivalent to array[(0, 1)], equivalent to array[1])
 return array[0, 1];
}

C++23 では、添字式が null 以外の任意の数の引数を受け入れることができるように、構文を再利用したいと考えています。これは、mdspan と mdarray のインターフェイスをより直感的にするために重要です。これらのクラスは現在、呼び出し演算子をオーバーロードしています。野生の演算子のオーバーロード。線形代数、画像操作、音声など、多くのドメインがこの機能から恩恵を受ける可能性があります。

#include <boost/multi_array.hpp> #include <type_traits>#include <vector>

template <typename T, std::size_t N>
class mdarray : protected boost::multi_array<T, N> {
public: 
using base =boost::multi_array<T, N>; base::base の使用; template <typename... Idx> requires (sizeof...(Idx) ==N &&(std::is_nothrow_convertible_v<Idx, std::size_t> &&...)) mdarray(Idx... idx) :ベース( boost::array<typename base::index, N>({idx...})) {};
 // variadic operator []
 template <typename... Idx>
 requires (sizeof...(Idx) == N
 && (std::is_nothrow_convertible_v<Idx, std::size_t> && ...))
 T & operator[](Idx... idx) {
 boost::array<typename base::index, N> id({idx...});
 return this->operator()(id);
 }
};

int main() {
 mdarray<int, 2> arr(2, 2);
 arr[1, 1] = 42;
 return arr[1, 1];
}

この機能は P2128R0 - 多次元添字演算子で説明されており、今後の会議で C++ 委員会に提示される予定です。

名前のないプレースホルダー

ネーミングは難しい。気にしない変数に名前を付けるのはさらに困難です。C++ では変数名が問題にならない場合がいくつかあります。例:

  • 手動でロック解除されないミューテックス ロックなど、あらゆる種類の RAII ガード
std::unique_lock my_lock(m);
  • 構造化バインディングの一部の値
auto [result, i_dont_care] = my_map.insert(42);
  • 有効期間を延長するためにラムダ キャプチャに格納される変数
std::unique_ptr<T> ptr = /*...*/;
auto& field1 = ptr->field1;
auto& field2 = ptr->field2
[really_do_not_care=std::move(ptr), &field1=field1, &field2=field2](){...};

(P1110 から盗んだ例)

  • 自己登録およびその他の副作用に使用されるグローバル変数

この最後の例は、多くの場合、14 で一意の識別子を作成しようとするマクロにラップされています。 と 22 グローバルスコープで。

auto CONCAT(__register_foobar_, __LINE__, __COUNTER__) = register_type<Foo>("Foo");

多くの言語は 32 を使用します Go、Rust、Scala、Haskell など、「名前はどうでもいい」という意味の魔法の識別子としての識別子。 Python も同様に 47 を使用します 慣例により同じ方法です。

残念ながら、55 は現在 C++ では予約されておらず (グローバル名前空間を除く)、GoogleTest などのいくつかのフレームワークで使用されており、これも「気にしない」という意味です。

P1110 は、 65 などのいくつかの代替構文を考慮します 、 7387 .しかし、私は 96 だと思います その目的のための最も洗練された識別子です。可読性と言語間での一貫性の両方のために、これを使用するよう努めるべきです。これは、可能であれば重要だと思います。

P1469 - Disallow _ Using C++20 for Pattern Matching in C++23 notes、

なぜ 101 なのか 115のときはとても重要 利用可能です?パターン マッチングを使用する言語では、ほぼ例外なく 120 が使用されます。 ワイルドカード パターンと C++ の一般的なライブラリ (Google Test など) は同じことを行います。 C++ がそのようなユビキタスなトークンを使用しないとしたら、それはぎこちなく、やや恥ずかしいことです。さらに、 131 なので 140 は広く使用されているため、人々は 140 を使用することを期待しています。 とにかく、誤って 152 をバインドします

幸い、163 を使用できる方法があります。 プレースホルダー識別子として、名前空間スコープの変数識別子として使用するいくつかのライブラリを壊すことはありません:

176 を作ることができます 180 の場合のみ魔法 範囲内に既に存在します。別名、それは 2 回目の使用でのみ魔法になります。このソリューションは、名前のないキャプチャ、構造化バインディング、および RAII ガードに対して非常にうまく機能し、既存のコードを壊さないように慎重に回避します。


#include <map>
int main() {
 std::map<int, int> m;
 auto [it, _] = m.emplace(0, 42);
 auto [_, value] = *it;
 return value;
}

もちろん、190 のもう 1 つの使用例です。 202 とマークされているかのように、未使用の変数をサイレントにすることです。 :


[[nodiscard]]
int f() {
 return 42;
}

int main() {
 auto _ = f();
 // result discarded
 f();
 // unused variable
 auto foo = f();
}

214 のいくつかの使用法を非推奨にすることができます 特に型、概念、モジュール、エイリアスなどの識別子として。

このアプローチの欠点は、223 によって導入された変数が 匿名かどうか。しかし、これらのケースは十分に診断できます。


struct raii {
 raii();
};

int main() {
 int _ = 42;
 raii _;
 return _; // warning: Refering to a variable named '_'
 // while anonymous variables are in scope
}

リンケージと ODR の問題のため、239 魔法の空白の識別子は名前空間スコープでは使用できないため.ただし、エクスポートされていない場合はモジュール単位で許可できます。これは、初期化の副作用にのみ使用される変数を宣言するのに非常に役立ちます.


export module m;

int _ = 42;
int _ = 47;

これらの変数には特別なマングリングが必要になるため、これはまだ完全には実装されていないことに注意してください。

EWG-私は、P1110 で説明されているようなプレースホルダー名の一般的なアイデアに興味を持っているようでした.ただし、ここで説明されている特定の動作に関する提案はまだありません.Varna のいくつかの論文と協力できるかどうかを確認します.

以上です

これらは小さな機能ですが、言語をもう少し直感的にするのに役立ちます。

ご意見をお聞かせください!

Matt Godbolt と Compiler Explorer チームの他のメンバーに多大な感謝を捧げます。