Rokiのチラ裏

学生による学習のログ

aggregate, aggregate initialization まとめ

C++ における aggregate と aggregate initialization についてのメモ。
まずバージョンごとの aggregate の定義から。

aggregate

C++03

aggregate とは*1

  • 配列または
  • 以下の要件を満たすクラスである
    • ユーザー定義コンストラクタがない
    • private または protected な非 static data member がない
    • 基底クラスがない
    • 仮想関数がない

C++11

aggregate とは*2

  • 配列または
  • 以下の要件を満たすクラスである
    • ユーザー定義コンストラクタがない(明示的にデフォルト、または削除されたコンストラクタを除く)
    • 非 static data member に対する brace-or-equal-initializer*3 がない
    • private または protected な非 static data member がない
    • 基底クラスがない
    • 仮想関数がない

C++11 から default member initializer が導入されたが、同機能を用いた場合そのクラスは aggregate ではない。

C++14

aggregate とは*4

  • 配列または
  • 以下の要件を満たすクラスである。
    • ユーザー定義コンストラクタがない(明示的にデフォルト、または削除されたコンストラクタを除く)
    • private または protected な非 static data member がない
    • 基底クラスがない
    • 仮想関数がない

C++14 では 非 static data member に対する brace-or-equal-initializer があっても(default member initializerを用いても) aggregate と見なされる。

struct X{ // aggregate
    int x = 42;
};

C++17

aggregate とは*5

  • 配列または
  • 以下の要件を満たすクラスである。
    • ユーザー定義の明示的、または継承されたコンストラクタがない(明示的にデフォルト、または削除されたコンストラクタを除く)
    • private または protected な非 static data member がない
    • 仮想関数がない
    • 仮想、private 及び protected な基底クラスがない

C++17 では基底クラスを持っていてもそれを public で継承しているのであれば aggregate であると見なされる。この時、aggregate initialization では、protected または private な基底クラスの member またはコンストラクタへのアクセスは許可されない。

struct X{
    int a;
};
struct Y:X{ // aggregate
    int b;
};

またこの仕様により、ある aggregate クラスの基底型と convertible であるオブジェクトBで aggregate クラスDのオブジェクトの初期化を行える。

struct B{};
struct D:B{};
struct A{
    operator B() { return {}; }
};

D{B{}}; // OK: C++17
D{A{}}; // OK: C++17

この機能を利用して例えば aggregate クラスが基底クラスを持つかどうか検出する事ができる。

stackoverflow.com

まずテンプレートパラメータの適切な基底クラス型に変換できるクラステンプレートを作成する。

template<class T>
struct any_base{
    operator T() = delete;
    template<class U, class  = std::enable_if_t<std::is_base_of_v<U,T>>>
    operator U();
};

次にテンプレートパラメータTany_base型の値から constructible な aggregate であるかどうかを検出する。

template<class, class = void>
struct has_any_base : std::false_type{};

template<class T>
struct has_any_base<T, std::void_t<decltype(T{any_base<T>{}})>> : std::true_type {};

以下に動作の概要を示す

  • implicit conversion によってTの初期化を試みる
  • any_base をTそのものに変換する implicit conversion の member function は削除されているため any_base から T の値は取得できない。
  • any_base から Tの値は取得できないためTのそれ以外の初期化方法を考慮する必要があるが、返される型はstd::is_base_ofによって継承関係が要求されているため、その要件を唯一満たすのは、aggregate クラスの基底型のオブジェクトでその aggregate クラスの派生型のオブジェクトを初期化する事である。
    • それが不可能である場合、推論から外れる
    • それが可能である場合、Uは aggregate クラスTの基底型に推論される
  • 候補がある場合、substitution に成功、候補がない場合、substitution に失敗する。

C++20

C++17 と変わらない*6

aggregate initialization

aggregate 型のオブジェクトは、braced-init-list を使って初期化する事ができる。構文は以下の通りである。

T object = { arg1, arg2, ...};

C++11 で uniform initialization が導入されたため、C++11 以降では以下の構文も適用できる。

T object{arg1, arg2, ...};

C++20 で Designated initializers が導入されたため、C++20 以降では以下の構文も適用できる。

T object = { .designator = arg1, .designator{arg2}...};
T object{ .designator = arg1, .designator{arg2}...};

Designated initializers

C++20 から追加される Designated initializers の構文を以下に示す。n4687 11.6 Initializers [dcl.init] から抜粋

initializer:\\
\hspace{8pt}brace-or-equal-initializer\\
\hspace{8pt}( expression-list )

brace-or-equal-initializer:\\
\hspace{8pt} = initializer-clause\\
\hspace{8pt}braced-init-list

initializer-clause:\\
\hspace{8pt}assignment-expression\\
\hspace{8pt}braced-init-list

braced-init-list:\\
\hspace{8pt}\{initializer-list ,_{opt} \} \\
\hspace{8pt}\{designated-initializer-list ,_{opt} \}\\
\hspace{8pt}\{ \}

initializer-list:\\
\hspace{8pt}initializer-clause\ldots_{opt}\\
\hspace{8pt}initializer-list, initializer-clause\ldots_{opt}

designated-initializer-list:\\
\hspace{8pt}designated-initializer-clause\\
\hspace{8pt}designated-initializer-list, designated-initializer-clause

designated-initializer-clause:\\
\hspace{8pt}designator\hspace{2pt}brace-or-equal-initializer

designator:\\
\hspace{8pt}. identifier

expr-or-braced-init-list:\\
\hspace{8pt}expression\\
\hspace{8pt}braced-init-list

designatorTの direct non-static member に名前を付ける必要があり、式で使用される全てのdesignatorsTの data member と同じ順序で現れなければならない。

struct X{ int x; int y; int z; };
A a{.y = 2, .x = 1}; // エラー! designator の順序が宣言の順序と一致しない
A b{.x = 1, .z = 2}; // OK: b.y は 0で初期化される

designated-initializer-list を用いて初期化する時、それらは designator の後に続く、それぞれが対応する brace-or-equal-initializer によってコピー初期化される。もし initializerassignment-expression または = assignment-expressionの形式であり、式を変換するために narrowing conversion*7 が必要である場合プログラムは ill-formed となる。designated initializer を利用して、union を以下のように最初の状態以外の状態に初期化する事ができる。union に対しては、initializer を1つだけ指定できる。

union U{ int a; const char* b; };
U f = {.b = "abcd"}; // OK: f の active member は b
U g = {.a = 1, .b = "abcd"}; // エラー!1つの initializer のみ利用できる

非 union aggregate である場合、明示的に初期化された要素ではない各要素は次のように初期化される。

  • 要素にデフォルトの default member initializer がある場合、要素はその initializer から初期化される。
  • それ以外の場合、要素が参照できない場合、要素は空の initializer list からコピー初期化される。
  • それ以外の場合、プログラムは ill-formed である。
struct A {
  int x;
  struct B {
    int i;
    int j;
  } b;
} a = { 1, { 2, 3 } }; // initializes a.x with 1, a.b.i with 2, a.b.j with 3
struct base1 { int b1, b2 = 42; };
struct base2 {
  base2() {
    b3 = 42;
  }
  int b3;
};
struct derived : base1, base2 {
  int d;
};

derived d1{{1, 2}, {}, 4}; // initializes d1.b1 with 1, d1.b2 with 2, d1.b3 with 42,d1.d with 4
derived d2{{}, {}, 4}; // initializes d2.b1 with 0, d2.b2 with 42, d2.b3 with 42, d2.d with 4

union aggregate であり、initializer list が空の場合

  • 任意の variant member が default member initializer を持つ場合、その member はその default member initializer から初期化される。
  • それ以外の場合は、共用体の最初のメンバー(存在する場合)が空の初期化子リストからコピー初期化される。

    指定された  designated initializer clause で初期化された aggregate が匿名の union member を持つ場合、対応する designated initializer は、その匿名化された union の member の1つに名前を付ける必要がある。また、順序が不定である初期化、ネストされた designated initialization、指定された initializer と通常の initializer の混合、および配列の指定された初期化は全てC99でサポートされる機能であるが、C++20では許可されない。
struct A { int x, y; };
struct B { struct A a; };
struct A a = {.y = 1, .x = 2}; // valid C, invalid C++ (out of order)
int arr[3] = {[1] = 5};        // valid C, invalid C++ (array)
struct B b = {.a.x = 0};       // valid C, invalid C++ (nested)
struct A a = {.x = 1, 2};      // valid C, invalid C++ (mixed)

*1:C++ Standard - ANSI ISO IEC 14882 2003 8.5.1 Aggregates [dcl.init.aggr]/paragraph 1

*2:n3337 8.5.1 Aggregates [dcl.init.aggr]/paragraph 1

*3:= assignment-expression または = braced-init-list または braced-init-list

*4:n4140 8.5.1 Aggregates [dcl.init.aggr]/paragraph 1

*5:n4660 11.6.1 Aggregates [dcl.init.aggr]/paragraph 1

*6:執筆時現在では不確定要素を含むため後に変更される可能性がある

*7:11.6.4 List-initialization [dcl.init.list]/paragraph 7

n4687 C++ extensions for Concepts

このエントリは、ドラフト入りしたコンセプト仕様全体を網羅するためのものである。尚、このエントリは P0734R0 と同提案が drafting された n4687 に基づいており、注釈などで付けられるセクション名は特に指定のない限り n4687 を示しているものとする。

コンセプト

コンセプトとはテンプレート引数に制約を定義するテンプレートである*1concept キーワードは entity として追加され*2、以下のようにして定義する*3。これを concept-definition という。

template<template-parameter-list> concept concept-name = constraint-expression;

constraint-expressionconstraint-parameter は以下の通り。

template<typename T>
requires C<T>     // C は constraint-expression f1(T) を制約する
T f1(T x) { return x; }

template<C T>       // C は constraint-parameter として f2(T) を制約する
T f2(T x) { return x; }
  • concept-definition はコンセプトを宣言する。その識別子は、そのコンセプトの範囲内でそれを参照する concept-name となる*4
  • concept-definition名前空間のスコープ([basic.scope.namespace])に現れなければならない*5
  • 再帰的に自分自身を参照することはできない。
template<typename T>
concept C = C<T*>; // エラー: recursive concept
  • associated constraint を持たない*6
template<class T> concept bool C1 = true;

template<C1 T> 
concept C2 = true; // エラー: C1 Tは associated constraint を宣言する

template<class T> requires C1<T>
concept C3 = true; // エラー: requires節は associated constraint を宣言する
  • コンセプトの特殊化を示す id-expression(コンセプト名に対して型を指定した形式)は、コンパイル時に normalize を行い、その constraint が満たされていれば真、そうでなければ偽 の bool 型の prvalue を結果としてもたらす*7
template<typename T> concept C = true;
static_assert(C<int>); // OK
  • コンセプトによる constraint は、template name と overload resolution を使用する場合にも考慮され、constraint の partial order 中に比較される
  • インスタンス化する事はできない。明示的にインスタンス化したり、明示的に特殊化したり、部分的にコンセプトを特殊化するプログラムは ill-formed である*8constraint の元の定義の意味は変更できない。以下のいずれの場合も、fの constraint は満たされない。
void f(int) requires false;

f(0); // error: cannot call f

void (*p1)(int) = f; // error: cannot take the address of f

decltype(f)* p2 = nullptr; // error: the type decltype(f) is invalid
// fが評価されていないオペランドであってもそれらの constraint を満たさなければならない
  • concept-definition の最初に宣言されたテンプレートパラメータは、prototype parameter という。variadic concept は、その prototype parameter がテンプレートパラメータパックであるコンセプトである*9

requires

以下の内容は特に指定のない限り 8.1.7 Requires expressions [expr.prim.req] を参照している。キーワードrequiresは、テンプレートファミリの requires-clauseprimary-expression として追加される

  • requires-clause はテンプレート引数または関数宣言の constraints を指定する事ができる。
  • require-expression は、テンプレート引数に関する要件を簡潔に表現する方法を提供する。

要件は、name lookup でチェックするか、型と式のプロパティをチェックすることで確認できる*10。キーワード、requiresに対する構文定義は以下の通りである。

requires-expression:
    requires requirement-parameter-listopt requirement-body
requirement-parameter-list:
    ( parameter-declaration-clauseopt )
requirement-body:
    { requirement-seq }
requirement-seq:
    requirement
    requirement-seq requirement
requirement:
    simple-requirement
    type-requirement
    compound-requirement
    nested-requirement

requires-clause

requires-clause の構文定義は以下の通りである。

requires-clause:
    requires constraint-logical-or-expression
constraint-logical-and-expression:
    primary-expression
    constraint-logical-and-expression && primary-expression
constraint-logical-or-expression:
    constraint-logical-and-expression
    constraint-logical-or-expression || constraint-logical-and-expression

このように requires-clauseconstraint-logical-or-expression のみ受け付ける構文となっており、constraint-logical-or-expressionconstraint-logical-and-expressionprimaty-expression を内包する設計になっている。また、requires-clause は以下のような箇所に記述できる。

template<typename T>
void f(T&&) requires Eq<T>; // 関数宣言子の最後の要素として利用できる
 
template<typename T> requires Addable<T> // またはテンプレートパラメータリストの直後に利用できる
T add(T a, T b) { return a + b; }

この場合、キーワードrequiresに対して何らかの定数式が続かなければならない。例えばrequires true;と書くこともできる。定数式は上記構文の通り、以下のいずれかでなければならない。

  • primary-expression
  • 演算子 && で結合された primary-expression のシーケンス
  • 演算子 || と結合された前述の式のシーケンス

尚、仮想関数が requires-clause を持つ事はできない。

struct A {
    virtual void f() requires true; // error: constrained virtual function
};

requires-expression

requires-expressionは、以下で述べられる bool 型の prvalue である。requirement-body に現れる式は評価されないオペランドである。

simple-requirement

simple requirement:
    expression;

simple-requirement は、式の妥当性を主張できる。テンプレート引数の式への置換に失敗した場合、包含する requires-expression は false に評価される。

template<typename T> concept C =
requires (T a, T b) {
a + b; // C<T> is true if a + b is a valid expression
};

type-requirement

type-requirement:
    typename nested-name-specifieropt type-name ;

type-requirement は、型の妥当性を主張できる。テンプレート引数の式への置換に失敗した場合、包含する requires-expression は false に評価される。

template<typename T, typename T::type = 0> struct S;
template<typename T> using Ref = T&;
template<typename T> concept C =
    requires {
        typename T::inner; // required nested member name
        typename S<T>; // required class template specialization
        typename Ref<T>; // required alias template substitution, fails if T is void
     };

compound requires

compound-requirement:
    { expression } noexceptopt return-type-requirementopt ;
return-type-requirement:
    trailing-return-type
    -> cv-qualifier-seqopt constrained-parameter cv-qualifier-seqopt abstract-declaratoropt

compound-requires は式Eの特性をアサートする。テンプレート引数の置換(もしあれば)と意味論的特性の検証は、以下の順序で進行する。

  • テンプレート引数(存在する場合)を式に substitution する。
  • noexcept 指定子が存在する場合、Eは potentially-throwing な式ではないか。
  • return-type-requirementが存在する場合、
    • テンプレート引数(ある場合)をreturn-type-requirementに substitution する。
    • return-type-requirementtrailing-return-type の場合、Eは trailing-return-type で指定された型に暗黙的に変換可能であるか。変換が失敗した場合、包含する requires-expression は false である。
    • return-type-requirementconstraint-parameter で始まる場合、式は 17.8.2.1*11 の規則を使用して生成された関数テンプレート Fに対して演繹される。F は、単一の型のテンプレートパラメータTが constraint-parameter で宣言された void 関数テンプレートである。constrained-parameter から constと volatile 指定子の和集合を取って、新しい cv-qualifier-seq cvを作成する。 F にはtype-specifierが cv T で、その後に abstract-declarator が続く単一のパラメータがある。演繹が失敗した場合、含包する requirement-expression は false である。
template<typename T> concept C1 =
    requires(T x) {
        {x++};
    };

C1 の compound-requirement は、式x++が有効であることを要求する。これは、同じ式を持つ simple-requirement に相当する。

template<typename T> concept C2 =
    requires(T x) {
        {*x} -> typename T::inner;
    };

C2 の compound-requirement には、*xが有効な式であり、typename T::innerが有効な型であり、*xtypename T::innerに暗黙的に変換可能である事を要求する。

template<typename T, typename U> concept C3 = false;
template<typename T> concept C4 =
    requires(T x) {
        {*x} -> C3<int> const&;
    };

template<C3<int> X> void f(X const&); // #1

C4 の compound-requirement は、*xが生成された関数の引数として演繹される事を要求する。この場合、C3 は常に false であるため、演繹は常に失敗する。

template<typename T> concept C5 =
    requires(T x) {
        {g(x)} noexcept;
    };

C5 における compound-requirement は、g(x)が有効な式であり、g(x)が nonthrowing である事を要求する。

用語の定義

constraint

constraint は、logical operands のシーケンスであり、テンプレート引数の要件を指定するオペランドである。また、logical operation(論理演算) のオペランドは constraints である。constraint は

  • conjunction
  • disjunction
  • atomic constraint

の 3種類である。

associated constraint

  • 関連づけられた constraint を associated constraint という。
  • constaint 付きのテンプレートをインスタンス化するには、関連づけられた constraint 条件を満たす必要がある。
  • “関連づけられた” constraint は以下のように定義される。
    • constraint-expression がない場合、宣言には associated constraint がない。
    • それ以外の場合で、導入された constraint-expression が1つだけ存在する場合、constraint-expression はその式の normal form となる。
    • それ以外の場合、associated constraint は、オペランドが次の順序にある​ logical and expression の normal form である。
      • 宣言の template-parameter-list 内の各 constraint 付きパラメータによって導入された constraint-expression が外観順に並べ替えられ
      • template-parameter-listに続くrequire-clauseによって導入された constraint-expression
      • 関数宣言末尾の requires-clause によって導入された constraint-expression
template<typename T> concept C1 = sizeof(T) == 1;
template<typename T> concept C2 = C1<T>() && 1 == 2;
template<typename T> concept C3 = requires { typename T::type; };
template<typename T> concept C4 = requires (T x) { ++x; }

template<C2 U> void f1(U);      // The associated constraints are sizeof(T) == 1 (with mapping T↦U) ∧ 1 == 2. 
template<C3 U> void f2(U);      // The associated constraints are requires { typename T::type; } (with mapping T↦U). 
template<C4 U> void f3(U);      // The associated constraints are requires (T x) { ++x; } (with mapping T↦U).

*12

優先順序関係

コンセプトはオーバーロード解決における順序関係の決定に対して作用する言語機能の1つである。主に [over.match] 等で定義されるオーバーロード解決の作用に加えてコンセプトの機能がその優先順序に影響を与える。

オーバーロード候補の関数を f1, f2 とする時、

  •  f1, f2 の両者とも constraints を持っていて、constraints  X を包含する constraints  P ( X \subset P または  P \supset X)を  f1 が、constraints  X f2 が持っている時、 P is more constrained than  X が成り立ち、 f1 が優先される。
  •  f1 のみ constraints を持っている時、f1 が優先される。
  • そうでない場合、どちらも優先されない → コンセプトによる順序関係は成り立たない。


constraint には、前述した通り conjunction と disjunction (合接と離接)という2つの二項論理演算*13、と1つの部類があり、それぞれの constraint ごとに規定が定められている。

conjunction

  • ある constraint  P Q の conjunction ( P \land Q) は、P && Qとして指定する。
  • 2つの constraint の conjunction は、両方の constraints が満たされる場合にのみ満たされる。conjunction は左から右に評価され、短絡される。例えば left constraint(左の制約) が満たされない(not satisfied)場合、right constraint(右の制約) へのテンプレート引数の置換は行われない*14
  • constraint conjunction における演算子&&のユーザー定義オーバーロードは許可されない。
  • (1  P \land Q is more constrained than  P (2  P \land Q is more constrained than  Q である時 (1 と (2 が成り立つ。
template<class T>
concept Arithmetic = std::is_arithmetic_v<T>;
template<class T>
concept SignedArithmetic = Arithmetic<T> && std::is_signed_v<T>;
template<class T>
concept UnsignedArithmetic = Arithmetic<T> && !SignedArithmetic<T>;
template<class T>
concept C = false && UnsignedArithmetic<T>; // The right constraint(UnsignedArithmetic<T>) is never substituted.

void f(Arithmetic); /* Never called because concept of SignedArithmetic is a <i>more constrained</i> than concept of Arithmetic.
 'Arithmetic ∧ std::is_signed_v<T> (SignedArithmetic)' is <i>more constrained</i> than 'Arithmetic'. */
void f(SignedArithmetic);
void f(UnsignedArithmetic);

*15

disjunction

  • ある constraint  P Q の disjunction ( P \lor Q) は、P || Qと指定する。
  • いずれかの constraints が満たされれば、2つの constraint の disjunction が成立する(satisfied)。離接は左から右に評価され、短絡される。例えば left constraint(左の制約) が満たされている場合、right constraint(右の制約) への template argument deduction は試行されない)。
  • constraint disjunction における演算子||のユーザー定義オーバーロードは許可されない。
  • (1  P is more constrained than  P \lor Q (2  Q is more constrained than  P \lor Q である時 (1 と (2 が成り立つ。
template<class T>
concept C = true || std::is_same_v<T,int>; // Template argument deduction into the right constraint(std::is_same_v<T,int>) is never attempted.

*16

atomic constraint

  • conjunction でも disjunction でもない constraint が atomic constraint である。
  • constraint expression は logical and または logical or でもない。
  • Atomic constraints は constraint normalization によって形成される。
  • 2つの atomic constraint が同じ式から形成され、パラメータマッピングのターゲットが [temp.over.link] の式の規則*17に従って同等である場合、2つの atomic constraint は同一の constraint である。それ以外の場合は、異なる constraint である。
template<typename T> concept C = sizeof(T) == 4 && !true;      // requires atomic constraints sizeof(T) == 4 and !true

template<typename T> 
struct S {
  constexpr operator bool() const { return true; }
};

template<typename T> requires (S<T>{})
void f(T);                      // #1
void f(int);                    // #2

void g() {
  f(0);                         // error: expression S<int>{} does not have type bool
}                               // while checking satisfaction of deduced arguments of #1;
                                // call is ill-formed even though #2 is a better match

順序関係の生成

各 constraint の強弱を比較するためには associated constraint を normalize して normal form を生成しある規則に則って constraint 間の subsume 関係を成立し、その関係性から順序関係を成り立たせるプロセスが必要である。

normal form

式 Eの normal form とは、次のように定義される constraint である。

  • 式(E) の normal form は、Eの normal form である。
  • normal form の式E1 || E2は normal form の E1 と E2 の disjunction である。
  • normal form の式E1 && E2は normal form のE1 と E2 の conjunction である。
  • id-expressionC<A1, A2, ..., An>の normal form は、Cがコンセプト名である時、CのそれぞれのテンプレートパラメータにA 1, A 2, …, A nを substituting した後のCの制約式の normal form であり各 atomic constraint の parameter mapping で使用される。そのような置換が無効である型または式になった場合、ill-formed; NDR
  • それ以外の任意の式 E の normal form は、式 E で構成された atomic constraint である。
  • normalization によって残るものは、predicate constraints, expression-constraints, type constraints, implicit conversion constraints などの atomic constraints に関する一連の conjunction および disjunction である。argument deduction constraints および exception constraints が含まれる。
  • normal form には以下の二つの種類があり、全ての constraint はどちらの normal form にも同時に解釈する事ができる。
    • conjunctive normal form → 各節が atomic constraint の disjunction である節の conjunction である形
    • disjunctive normal form → 各節が atomic constraint の conjunction である節の disjunction である形
conjunctive, disjunctive normal form

 A, B, C が atomic constraint であり、 A \land (B \lor C) という constraint-expression がある時、

  • constraint  A \land (B \lor C) は conjunctive normal form である。その conjunctive 節は A(B \lor C)である。
  • constraint  A \land (B \lor C) の disjunctive normal form は (A \land B) \lor (A \land C)である*18。その disjunctive 節は  (A \land B) (A \land C) である。

normalization は ill-formed を引き起こす場合がある*19

template<typename T> concept A = T::value || true;
template<typename U> concept B = A<U*>;
template<typename V> concept C = B<V&>;

T::valueがポインタ型Tに対して ill-formed であるにも関わらず コンセプトBの normalize は有効であり、T​::​value (with the mapping T↦U*) ∨ true (with an empty mapping)である。更に C の constraint-expression を normalize すると、パラメータマッピングに無効な型T&*が形成され、ill-formed NDR となる。

Partial ordering by constraints

partial ordering 関係における constraint 同士は以下のようにしてその constraint の強弱を定め(subsume するか否か)、more constrained 関係を構築する事ができる。
2つの constraint  P Q 間で more constrained 関係を定める時、constraint  P は、以下に述べられるように、 P Q を含み、 P および  Q の atomic constraint の同一性([temp.constr.atomic]/paragraph 2、atomic constraint の説明で前述した同一規則)までを決定することができる場合、別の constraint  Q を包含すると言われる(A constraint P is said to subsume another constraint Q)。

  • constraint  P を disjunctive normal form に normalize し、constraint  Q を conjunctive normal form に normalize する。
  •  P は、 P の disjunctive normal form の全ての disjunctive 節 P_i が conjunctive normal form  Q のすべての conjunctive 節  Q_j を包含(subsume)する場合にのみ、 Q を包含(subsume)する。
  • disjunctive 節  P_i 内の atomic constraint  P_{i_a} が存在し、 P_{i_a} が atomic constraint  Q_{j_b} を含む(subsume)ように conjunctive 節  Q_j 内に atomic constraint  Q_{j_b} が存在する場合に限り、P_iQ_j を包含(subsume)する。
  • atomic constraint  A, B は、[temp.constr.atomic] の同一規則において  A B が等価である場合にのみ、 B を包含(subsume)する。

     A, B を atomic constraint とした時、constraint  A \land B A を包含(subsume)するが、A A \land B を包含(subsume)しない。constraint  A A \lor B を包含(subsume)するが、 A \lor B A を包含(subsume)しない。また、すべての constraint がそれ自身を包含(subsume)している。

 P A \land (B \lor C) Q A \lor C とした時の P Q 間の順序関係を考える*20

  •  P を disjunctive normal form に normalize し  (A \land B) \lor (A \lor C) Q を conjunctive normal form に normalize し  A \lor C とする。この時、disjunctive 節  P_1 (A \land B) P_2 (A \land C) であり、conjunctive 節  Q_1 A \lor C である。
  •  P_1Q_1 を比較した時、 P_1 内の conjunction における left operand AQ_1 内の disjunction における left operand  A と atomic constraint の同一性規則([temp.constr.atomic]/paragraph 2])に則って同一であるため P_1 Q_1 を包含(subsume)する。また、 P_2Q_2 を比較した時、 P_2 内の conjunction における left operand A Q_1 の disjunction における left operand A と atomic constraint の同一性規則([temp.constr.atomic]/paragraph 2])に則って同一であるため、P_2 Q_1 を包含(subsume)する。
  • よって  P_1 subsume  Q_1 かつ*21  P_2 subsume  Q_1 より  P subsumes  Q

     P Q をそれぞれ入れ替え、 P A \lor C Q A \land (B \lor C) とする。

  •  P を disjunctive normal form に normalize し  A \lor C Q を conjunctive normal form に normalize し  A \land (B \lor C) とする。この時、disjunctive 節 P_1AP_2C であり、conjunctive 節 Q_1 AQ_2(B \lor C) である。

  •  P_1 Q_1 を比較した時、 P_1 内の  A Q_1 内の  A と atomic constraint の同一性規則([temp.constr.atomic]/paragraph 2])に則って同一であるため、P_1Q_1 を包含(subsume)する。また、P_1Q_2 を比較した時、P_1 内の  A Q_2 内の disjunction における left operand B 及び right operand C のどちらも atomic constraint の同一性規則([temp.constr.atomic]/paragraph 2])に則り同一ではないため、P_1 Q_2 を包含(subsume)しない。
  • よって  P_1 does not subsume  Q_2 より P does not subsume Q

    以上から  A \land (B \lor C) subsumes  A \lor C となり2つの constraints において  A \land (B \lor C) の優先順序が高いと言える。
    このように、これらの包含(subsume)関係は前述している通り constraint の partial order を定義しこの部分的な順序付けは、以下の5つの場合における決定に作用する。

  • 非テンプレート関数の中で最も有望な候補([over.match.best])の決定

  • 非テンプレート関数のアドレス([over.over])の決定
  • テンプレートテンプレート引数の一致
  • クラステンプレートの特殊化における partial order
  • 関数テンプレートにおける partial order

at least as constrained

以下の条件を満たす時、宣言 D1 は少なくとも宣言 D2 と同じくらい制約されている(A declaration D1 is at least as constrained as a declaration)と言う。

  • D1とD2は両方とも constraint 付きの宣言であり、D1 の associated constraint は D2 の constraint を包含(subsume)する。または、
  • D2 に associated constraint がない。

    また、宣言 D1 は、D1 が少なくとも D2 と同じくらい制約されていて(D1 is at least as constrained as D2)、D2 が少なくともD1 と同じくらい制約されていない(D2 is not at least as constrained as D1)場合、別の宣言D2よりも制約されている(A declaration D1 is more constrained than another declaration D2)という。

余談

コンセプトに対する反応諸々。

p0726r0 はコンセプトの理想と現実から始まり、SFINAEとstd::enable_ifが既にあるだろうとしている。尚、上記ツイートにあるもう一つのリンク p0724r0 には以下のように述べられている。

I am well aware of that, but I'm boldly suggesting that we take a brave step and send a message to the C++ community that we are serious about having Concepts in C++20. In fact, a leap so brave that we merge the current wording in and all commit to finishing it up design-wise, wording-wise and otherwise before C++20 goes out, without missing its schedule.

I'm also well aware that we don't usually merge new wording which has known issues (except when we do, but that happens much more rarely for Core than Library). Perhaps this facility is significant enough to make an exception.

*1:17.6.8 Concept definitions [temp.concept]/paragraph 1

*2:6.1 Basic concepts [gram.basic]

*3:17 Templates [temp]/paragraph 1

*4:17.6.8 Concept definitions [temp.concept]/paragraph 2

*5:17.6.8 Concept definitions [temp.concept]/paragraph 3

*6:17.6.8 Concept definitions [temp.concept]/paragraph 4

*7:8.1.4 Names [expr.prim.id]/paragraph 3

*8:17.6.8 Concept definitions [temp.concept]/paragraph 5

*9:17.6.8 Concept definitions [temp.concept]/paragraph 6

*10:8.1.7 Requires expressions [expr.prim.req]

*11:17.8.2 Explicit instantiation [temp.explicit]

*12:17.4.3 Constraint normalization [temp.constr.normal]/paragraph3

*13:これらの論理演算には対応するC++構文がないため、ドラフト文書では解説の目的で、記号  \land を使用して conjunction (合接) を示し、記号  \lor を使用して disjunction (離接) を示す事がある。これらのオペランドは、left operand または right operand と呼ばれる。制約 A ∧ B では、A が left operandであり、Bが right operand である。

*14:これにより immediate context 外での substitution による諸々の hard error とかを防止できる。諸々の hard error については https://twitter.com/530506/status/907423269417914368 などを参照

*15:17.4.1.1 Logical operations [temp.constr.op]/paragraph 2

*16:17.4.1.1 Logical operations [temp.constr.op]/paragraph 3

*17:[temp.over.link]/paragraph 5, 同セクション/paragraph 6, 同セクション/paragraph 7 等を参照

*18:conjunctive normal form に分配法則を適用する

*19:17.4.3 Constraint normalization [temp.constr.normal]/paragraph1.4

*20:一見して強い constraint が分かりうるパターンではあるが、例として17.4.4 Partial ordering by constraints [temp.constr.order]/paragraph 2/footer note 138 に記載されている  A \land (B \lor C) を流用している。これに特別な意味はない。

*21:ドラフト上で示されている logical and ( \land) との混同を防ぐために敢えて"かつ"としている

Can non-type template parameters in c++17 be decltype(auto)?

stackoverflow.com

template <decltype(auto)> // Is this legal by standard?
struct X {};

結論:
合法である。

n4659 17.1 Template parameters [temp.param]/paragraph 4 に non-type template parameters が持てる型について示されている(下線部は強調)。

A non-type template-parameter shall have one of the following (optionally cv-qualified) types:
- integral or enumeration type,
- pointer to object or pointer to function,
- lvalue reference to object or lvalue reference to function,
- pointer to member,
- std​::​nullptr_­t, or
- a type that contains a placeholder type.

“a type that contains a placeholder type” に関する記述 同ドラフト 10.1.7.4 The auto specifier [dcl.spec.auto]/paragraph 1

The auto and decltype(auto) type-specifiers are used to designate a placeholder type that will be replaced later by deduction from an initializer. The auto type-specifier is also used to introduce a function type having a trailing-return-type or to signify that a lambda is a generic lambda ([expr.prim.lambda.closure]). The auto type-specifier is also used to introduce a structured binding declaration.

autodecltype(auto)型指定子は、後で初期化子から差し引かれる placeholder type を指定するために使用され、同セクション/paragraph 5

A placeholder type can also be used in the type-specifier-seq in the new-type-id or type-id of a new-expression and as a decl-specifier of the parameter-declaration's decl-specifier-seq in a template-parameter.

placeholder type はテンプレートパラメータ内のパラメータ宣言 decl-specifier-seqの宣言指定子として使用できる。

non-type template parameter におけるデータの受け渡しから TMP で実数を含む value computing を行う

push してからしばらく経っているが記録もかねて。C++ の non-type template parameter には浮動小数点型などの数値を直接与えることはできない。しかし、型の内部に内包させて Expression templates のようにその値を取り出そうとした瞬間に型情報に紐付いている式の形を展開させられれば実質 template arguments を利用してでも浮動小数点数値を受け渡し、かつ value computing を行う事ができる。基本的に、TMP はそれを純粋関数型言語とみなして記述するので*1、式の形を保持しておき遅延評価を行うという解はまぁ当然と言える。実装そのものはそこそこ小さくまとまっていると思われる。これを利用すると、以下のように浮動小数点などの値を non-type template parameter 経由で扱うことができる。

一応 Polish Notation と infix notation 的な両記法をサポートしているようなつもりでいる。上記のコードは特別意味のある計算をしていないが、例えばこれを使えば理論的には TMP によるコンパイルマンデルブロ集合を得る事ができたりする(コンパイル時Cコンパイラがあるので不思議な事ではないが)。

しかし残念ながらこのコードは実際には動かない。コード中冒頭のコメントにもあるとおり、メモリを大量に食い尽くしてしまうからだ。浮動小数点計算を non-type template parameter 経由で行えるようにしたところで特別何か使い道も思いつかないのだが、例えばコンパイル時にコサインテーブルを生成するぐらいであれば容易にできる。実際フルスクラッチで以前に書いた JPEG エンコーダ/デコーダのコサインテーブルはそれを利用している

*1:この辺りの C++ TMP における無駄な活用については any_packTMPによるコンパイル時乱数アルゴリズムの実装TMPによる四則演算パーサ等を参照されたい

stack overflow からの小ネタメモ #3

前回

stackoverflow.com C++98(03) ではstd::vectorの fill コンストラクタのプロトタイプに初期化子のデフォルト値があるが C++11 では削除されている。 C++03 – ISO 14882:2003 23.2.4.1 vector constructors, copy, and assignment [lib.vector.cons] から一部引用。

explicit vector(size_type n, const T& value = T(),const Allocator& = Allocator());

続いて N3337 23.3.6.2 vector constructors, copy, and assignment [vector.cons] から一部引用。

vector(size_type n, const T& value,const Allocator& = Allocator());

C++98(03) はプロトタイプオブジェクトを取り出し、それをn回コピーした。C++11では、n個のデフォルトオブジェクトが構築されるようになった事で、n個のコピーが削除され、n個のデフォルト構成に置き換えられる事となった。また、copy-constructible を要求する必然性はないのでそれが排除された。

stackoverflow.com

他のコンストラクターが有効でない場合にのみ呼び出される、variadic forwarding reference constructor の SFINAE 定義において以下のような解決策が講じられた。gcc 5.2.0 と clang 3.7.0 では意図した通りに動くが、これはどのように動作するのか、また合法か否か。

template <typename... T,typename = std::enable_if_t<!std::is_constructible<C, T&&...>::value>>
C(T&&... ) { }

現在の作業草案では、デフォルトのテンプレート引数がインスタンス化されるタイミングは記述されていない*1*2。興味深いのは、LWG 2452 でもあるようにそのセマンティックスを無視するかのような動作をする事だ。

struct C {
  C(const char*, size_t) {}
  template <class... Ts,
    typename = std::enable_if_t<!std::is_constructible<C, Ts&&...>::value>
  >
  C(Ts&&... ) { }
};

int main() {
  int a = 0;
  C x{a};
  std::cout << std::is_constructible<C, int&>{} << '\n'; // 0
}

これは、上述の理由により UB を引き起こす。LWG 2452 には Library specification でその旨を明記するよう呼びかけられている。

stackoverflow.com C++17 で<new>ヘッダに追加されたstd::launderの目的とは何か。コンパイラC++ のライフタイム規則*3に基づく最適化を行えるが、定数メンバでオブジェクトを再初期化する(placement new)と、それらの規則と上手く両立できない。std::launderは、ライフタイムルールとは無関係に、同じアドレスに以前存在したオブジェクトではなくアドレスが新しいオブジェクトのみを参照することができるコンパイラへのヒントである。オブジェクトモデルの観点からは、あるアドレスのある潜在的に寿命の長いオブジェクトへのポインタから、そのアドレスの現在有効なオブジェクトへのポインタへのポインタをマッピングする。実装の観点から見ると、コンパイラは、返されたポインタのバージョンが指すオブジェクトのアイデンティティについて何も知らないと仮定することを防ぐ事で最適化の障壁となる*4N4659 [ptr.launder] では*5、これを pointer optimazation barrier と称している。N4659 21.6.4 Pointer optimization barrier [ptr.launder] から引用。

template <class T> constexpr T* launder(T* p) noexcept;
Requires: p represents the address A of a byte in memory. An object X that is within its lifetime and whose type is similar to T is located at the address A. All bytes of storage that would be reachable through the result are reachable through p (see below).

Returns: A value of type T * that points to X.

Remarks: An invocation of this function may be used in a core constant expression whenever the value of its argument may be used in a core constant expression. A byte of storage is reachable through a pointer value that points to an object Y if it is within the storage occupied by Y, an object that is pointer-interconvertible with Y, or the immediately-enclosing array object if Y is an array element. The program is ill-formed if T is a function type or cv void.

[ Note: If a new object is created in storage occupied by an existing object of the same type, a pointer to the original object can be used to refer to the new object unless the type contains const or reference members; in the latter cases, this function can be used to obtain a usable pointer to the new object. See [basic.life]. — end note ]

[ Example:
struct X { const int n; };
X *p = new X{3};
const int a = p->n;
new (p) X{5};                       // p does not point to new object ([basic.life]) because X<200b>::<200b>n is const.
const int b = p->n;                 // undefined behavior
const int c = std::launder(p)->n;   // OK
— end example ]

尚引用にもある通り T が関数型または(及びcv修飾された)voidの場合、ill-formed である。引数の値が core constant expression で使用される場合、std::launderを core constant expression で使用することができる。また、constメンバーまたは参照メンバーを持たないオブジェクトの場合、lifetime ルールに基づきポインタと参照を再利用できるためstd::launderは必要ない。 続いて p0636r0 から Examples, notes を引用

A language support tool (an “optimisation barrier”) to allow libraries to reuse storage and access that storage through an old pointer, which was previously not allowed. (This is an expert tool for implementers and not expected to show up in “normal” code.)

ライブラリが以前許可していなかった古いポインタを使用してストレージを再利用し、そのストレージにアクセスできるようにする言語サポートツール(“最適化バリア”)。 これは実装者のための専門ツールであり、"通常の"コードでは見られないとある。続いて関連項目を引用。N4659 4.5 The C++ object model [intro.object] paragraph 1 から。

The constructs in a C++ program create, destroy, refer to, access, and manipulate objects. An object is created by a definition, by a new-expression, when implicitly changing the active member of a union, or when a temporary object is created ([conv.rval], [class.temporary]). An object occupies a region of storage in its period of construction ([class.cdtor]), throughout its lifetime, and in its period of destruction ([class.cdtor]). [ Note: A function is not an object, regardless of whether or not it occupies storage in the way that objects do. — end note ]

続いて N4659 [intro.object] paragraph 2

Objects can contain other objects, called subobjects. A subobject can be a member subobject ([class.mem]), a base class subobject ([class.derived]), or an array element. An object that is not a subobject of any other object is called a complete object. If an object is created in storage associated with a member subobject or array element e (which may or may not be within its lifetime), the created object is a subobject of e's containing object if

the lifetime of e's containing object has begun and not ended, and

the storage for the new object exactly overlays the storage location associated with e, and

the new object is of the same type as e (ignoring cv-qualification).

[ Note: If the subobject contains a reference member or a const subobject, the name of the original subobject cannot be used to access the new object ([basic.life]). — end note ] [ Example:
struct X { const int n; };
union U { X x; float f; };
void tong() {
  U u = {{ 1 }};
  u.f = 5.f;                          // OK, creates new subobject of u ([class.union])
  X *p = new (&u.x) X {2};            // OK, creates new subobject of u
  assert(p->n == 2);                  // OK
  assert(*std::launder(&u.x.n) == 2); // OK
  assert(u.x.n == 2);                 // undefined behavior, u.x does not name new subobject
}
— end example ]

以下は例。

#include <new>
 
struct X {
  const int n; // note: X has a const member
  int m;
};
int main()
{
  X *p = new X{3};
  const int a = p->n;
  new (p) X{5};       // p does not point to new object because X::n is const
  const int b = p->n; // undefined behavior
  const int x = p->m; // undefined behavior (even though m is non-const, p can't be used)
  const int c = std::launder(p)->n; // OK, std::launder(p) points to new object
}
#include <iostream>

int main() 
{
   alignas(int) char data[sizeof(int)];
   int *myInt = new (data) int;
   *myInt = 34;

   std::cout << *reinterpret_cast<int*>(data); // dereferencing type-punned pointer will break strict-aliasing rules 
   std::cout << *std::launder(reinterpret_cast<int*>(data)); // OK
}

std::launderにおける feature test macro は __cpp_lib_launder である。

*1:CWG 2008

*2:尚この問題は、is_constructible + default 関数/テンプレート引数の唯一の問題ではない。例えば、LWG2452を参照されたい。

*3:[basic.life]/paragraph 8

*4:https://groups.google.com/a/isocpp.org/forum/#!topic/std-discussion/ko5ceM4szIE

*5:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4430.htmlによる追加

C++ Standard Library Active Issues List Issue Status 和訳

C++ Standard Library Active Issues List の Issue Status の和訳。和訳されているのを見たことがなかったので、まとめておく。

様々なステータスを通じてLWG移行に報告された問題で、解決への進捗状況を示す。通常、ほとんどの問題は次の段階を流れる

  • New - この問題は、まだLWGによってレビューされていません。いかなる提案された決議も、問題提出者からの示唆であり、LWGの見解と解釈すべきではありません。
  • Open - LWGは問題について話し合ったが、問題を先に進める準備はできていない。公開ステータスにはいくつかの理由が考えられる。
    • この問題に対処する方法についてまだ合意に達していない可能性がある。
    • 非公式な合意に達したかもしれないが、LWGはレビューのために正確な提案された分限を待つ。
    • LWGは、先に進む前に追加の技術専門家に相談したい。
    • この問題には、さらなる研究が必要な場合がある。 未解決の問題に対する提案された解決策は、依然としてLWGの見解として解釈されるものではない。議論の現状に関するコメントは、公開されている問題の終わりにイタリック体で記載されることが多い。そのようなコメントは情報提供のみを目的としており、過度の重要性を与えるべきではない。
  • Review - LWGが以前に非公式な合意に達した問題について、提案された決議の正確な文言を検討することができるようになった。
  • Ready - LWGは、問題が標準の欠陥であり、提案された決議が正しいと合意に達し、問題が欠陥報告書(DR)としてのさらなる行動のために全委員会に転送する用意がある。 通常、問題はLWGレビュー中に変更されない文言が現在公開されている Issues List に提案された解決策を持っていなければならず、Ready状態に移行する必要がある。
  • Voting - このステータスは、公開されている Issues List には表示されないが、会議前のメールで問題が発生したことを示すためのマーカーで、提案された解決策は正しい。問題はワーキンググループに提供されます。現在の作業用紙(WP)に適用するか、または他の適切な方法でクローズされる。これにより、ミーティング自体の間に Ready 状態に移行する物と、それを次のミーティングまで転送してはならない物との区別が容易になる。問題に対する進捗がない場合は、次のリストが公開される前に、Open 状態に戻る。
  • Immediate - このステータスは、発行済みの Issues List には表示されない。会議前のメールで問題の準備が完了していなかったことを示すために会議中に使用するためのマーカーであるが、提案された解決策は正しい。また現在の作業用紙(WP)に適用するか、またはその他の適切な方法でクローズするために、現在の会議の終わりにワーキンググループに提出される。このステータスは、ごくまれにしか使用されない。通常、小規模で明白な fix であり、リリース予定の会議で使用される。問題に対する進捗がない場合は、次のリストが公開される前に、 Open 状態に戻る。
  • Deferred - LWGは問題を議論したが、まだ問題を進める準備はできていないか、標準や Technical Report の発行を遅らせるほど重要であるとは考えていない。一般的には、文面は技術的には正しいかもしれないが、ミスリーディングできてしまう言葉遣いを明確にすることに費やされる。 延期された問題の解決案は、依然としてLWGの見解として解釈されるべきではない。議論の現状に関するコメントは、公開されている問題の終わりにイタリック体で記載されることが多い。そのようなコメントは情報提供のみを目的としており、過度の重要性を与えるべきではない。
  • Core - LWGはこの問題について話し合い、問題解決の重要な部分は、標準のコア部分での言語のクリーンアップによってうまく処理されると感じている。この問題は、コアワーキンググループに渡され、理想的には、ライブラリの問題からリンクできる問題を Open しなければならない。このような問題は、コアが変更を行った後(または変更を拒否した後)に再確認される。
  • EWG - LWGはこの問題について話し合い、問題解決の重要な部分は、言語の拡張(上手くいけば小さな)によって処理されることがある。この問題は、Evolutionワーキンググループに渡される。このワーキンググループは、理想的には、ライブラリの問題からリンクできる問題を Open しなければならない。このような問題は、Evoltion が何らかの勧告を行った(または却下した)後に再確認される。
  • LEWG - LWGはこの問題について議論し、その問題が拡張されているとはいえないと考えているか、または fundamental でライブラリデザインを変更しているため、初期の作業を Library Evolution ワーキンググループに委任した。
  • DR - (Defect Report) - WG21 / PL22.16委員会は、潜在的な欠陥報告として処理するために問題をプロジェクトエディタに転送するよう投票した。プロジェクトエディタは問題をレビューし、それを WG21 Convenorに転送し、WG21 Convenor はそれを最終的な処分のために全委員会に返す。この Issues List は、プロセスのどこに関係なく、DRのステータスをこれらのすべての欠陥レポートに一致させる。
  • WP - (Working Paper) - 提案された決議は Technical Corrigendum として受け入れられていなかったが、WG21 / PL22.16委員会は、不具合報告書の提案された決議をワーキングペーパーに適用するよう投票した。
  • C++17 - (C++ Standard, as revised for 2017) - WG21 / PL22.16委員会は、C++標準、ISO / IEC IS 14882:2017(E) への 2017 改訂版に不具合報告書の提案された決議案を受け入れることに賛成した。
  • C++14 - (C++ Standard, as revised for 2014) - WG21 / PL22.16委員会は、C++標準、ISO / IEC IS 14882:2014(E) への 2014 改訂版に不具合報告書の提案された決議案を受け入れることに賛成した。
  • C++11 - (C++ Standard, as revised for 2011) - WG21 / PL22.16委員会は、C++標準、ISO / IEC 14882:2011(E) への 2011 改訂版に不具合報告書の提案された決議案を受け入れることに賛成した。
  • CD1 - (Committee Draft 2008) - WG21 / PL22.16委員会は、2008年秋の委員会草案に不具合報告書に提案された決議を受け入れることを表明した。
  • TC1 - (Technical Corrigenda 1) - WG21 / PL22.16委員会は、不具合報告書の提案された決議を Technical Corrigenda として受け入れることに賛成した。この問題に対するこれ以上のアクションは、ISOルールに基づき不可能である。
  • TRDec - (Decimal TR defect) - LWGは、不具合報告書の提案された決議を Decimal TR に受け入れることに賛成した。この問題に対する行動はこのように完全であり、それ以上の行動は期待されない。
  • TS - (TS - various) - WG21 / PL22.16委員会は、不具合報告書の提案された決議を公表された Technical Corrigenda に受け入れることに賛成した。
  • Resolved - LWGは、この問題が標準の欠陥であるという合意に達したが、問題を解決するために採択された決議は、リストのこの問題以外のメカニズムを介して行われた。
  • Dup - LWGは、この問題が別の問題と重複しているとの合意に達したため、今後は対処しない。
  • NAD - LWGは、この問題が標準の欠陥ではないという合意に達している。
  • NAD Editorial - LWGは、問題が編集上の扱いを受けることができるか、論文によって取り扱われるという合意に達している。
  • Tentatively - これはステータスへの修飾子である。この問題は、オンラインで、または非公式の会議でレビューされているが、公式の会議ではレビューされておらず、資格を取得するためのサポートがいくつか作成されている。暫定資格の問題は、同じ会議内の完全な委員会(準備ができている場合)に転送される可能性がある。Ready の問題とは異なり、小さな委員会で審議されてから完全な委員会に転送されます。一時的にステータスが認定されている場合でも、問題は引き続きアクティブと見なされる。
  • Pending - これはステータスへの修飾子である。これはステータスが前に付いたときに、問題が委員会によって処理されたことを示し、関連する不適合なステータスに問題を移行することが決定される。しかし、 logistical な理由から、問題の示された結果は、最新の作業記録にまだ現れていない。
  • NAD Future - LWGは、この問題が次回の改訂時に再検討されるべきだと考えている。これは現在、Library Evolutional ワーキンググループによって管理されている進行中の課題であり、このステータスのほとんどの問題はLEWGのステータスで再開された。
  • NAD Concepts - このステータスは、C++11の開発中の言語の進化を反映している。コンセプトと呼ばれる新しい機能に言語が移行し、テンプレートの指定と書き方が根本的に変わった。この言語機能はC++11プロジェクトの終わりに近づくにつれて削除されたが、この部分の言語設計を再検討する意思が明確に示されている。その開発中に、その機能の使用に関連する更新されたライブラリに対して、またはコンセプト機能の明示的な使用を必要とする修正を要求するいくつかの問題が開かれた。このような問題はすべてこのステータスで解消されており、この機能または類似の言語機能が将来の標準に戻った場合に再確認される可能性がある。
  • NAD Arrays - このステータスは、C++14/17の開発中の言語の進化を反映していた Arrays TS と呼ばれる技術仕様に関する作業が始まったが 2016年の初めにこの作業は放棄され、作業項目は正式に取り下げられた。 TSの開発中に、TSの機能がいくつか公開された。このような問題はすべてこのステータスで解消されており、この機能または類似の言語機能が将来の標準に戻った場合に再確認される可能性がある。

Issues List に最初に表示されるときに、問題のステータスは常にNewになる。 LWGが積極的に取り組んでいる間、それらは Open または Review に進むかもしれない。 LWGが問題の処理に関して合意に達すると、状態はDupNAD、または Ready に変更される。 PL22.16委員会が Ready の問題をプロジェクトエディタに転送すると投票すると、不具合報告書(DR)のステータスが与えられる。これらは、更新された標準(C++11、C++14)であるテクニカルコリレンダ(TC1)の基礎となる可能性がある。この LWG によるプロセスの目的は、スタンダードの真の欠陥である問題のみが正式なISO DRステータスに移行するためである。

non-type template parameter のシーケンス生成におけるプレースホルダなど

執筆時現在、C++1z(17)から追加される、non-type template parameter でのautoを使ってゴリゴリとコンパイルを行うと、コンパイル時間が型を指定している場合と比較して圧倒的に伸びる事があるので*1、この機能を使わずに C++14 までの機能だけで任意の型のシーケンス*2を生成できるように、以下のようなものを書いた記録。

github.com

これは、機能的にはstd::make_index_sequenceの上位互換であると言える。std::make_index_sequenceは、std::index_sequenceにシーケンスを溜め込む。つまりstd::size_tの値を持つシーケンスしか生成できない*3。これに対して、non-type template parameter が許容する任意の型を指定する事ができたり、値の適用時にプレースホルダのようなものが使えたりするようにした。

#include<srook/mpl/constant_sequence/algorithm/make_sequence.hpp>

まず、普通のシーケンス生成は以下のようにできる。これは、std::make_index_sequenceと変わらない。

 using type = srook::constant_sequence::make_sequence<10>;

生成される型は以下となる。

std::integer_sequence<unsigned long, 0ul, 1ul, 2ul, 3ul, 4ul, 5ul, 6ul, 7ul, 8ul, 9ul>

これを例えばint型のシーケンスにしたければ、以下のようにする。

using type = srook::constant_sequence::make_sequence<10,srook::constant_sequence::make_sequence_utility::cast<int>>;

この時生成される型は以下となる。

std::integer_sequence<int, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9>

また、同じ値で埋めたくなる事もあるだろう。以下のようにする。

using type = srook::constant_sequence::make_sequence<10,srook::constant_sequence::make_sequence_utility::initialize<int,42>>;

生成される型は以下となる。

std::integer_sequence<int, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42>

任意の操作をしたい事もあるだろう。その場合は、コンパイル時にデフォルトコンストラクトが可能でありかつコンパイル時に呼び出しが可能である operator() を持つクラスを渡す事で適用される*4

struct Plus2char{
        explicit constexpr Plus2char() = default;
        constexpr char operator()(std::size_t x) const noexcept
        {
                return char(x * 2);
        }
};

using type = srook::constant_sequence::make_sequence<10,Plus2char>;

生成される型は以下となる。

std::integer_sequence<char, (char)0, (char)2, (char)4, (char)6, (char)8, (char)10, (char)12, (char)14, (char)16, (char)18>

引数にシーケンスの値が渡ってくる。また、戻り型を変えれば、その値でシーケンスに溜め込むようにしてある。ここまではまぁ普通だと思うのだが、少し実験的な部分が以下の機能。

using namespace srook::constant_sequence::make_sequence_utility;

using type = srook::constant_sequence::make_sequence<10,decltype(std::integral_constant<int,2>() * placeholders::_val)>;

生成される型は以下となる。

std::integer_sequence<int, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18>

+-*/ をそれぞれこの機能向けに定義していて、呼び出されるとその演算処理の内容が埋め込まれた型を生成するだけなのだが、少し表現力豊かになったりする。
最後に、シーケンスのリバース機能。

using namespace srook::constant_sequence::make_sequence_utility;

using type = srook::constant_sequence::make_sequence<10,decltype(std::integral_constant<int,2>() * placeholders::_val),Reverse>;

以下のように生成される。

std::integer_sequence<int, 18, 16, 14, 12, 10, 8, 6, 4, 2, 0>

*1:原因究明中

*2:勿論浮動小数点数型などは non-type template parameter に渡せないので不可能

*3:std::make_index_sequence はそもそも”インデックス”のシーケンス生成を目的としているので、その用途としては十分に満たしている。

*4:以前大体同じような事をしているが、今回は auto を使っていない点で若干異なる