なぜrequireが必要なのですか?


161

C ++ 20の概念の隅の1つは、作成しなければならない特定の状況があることですrequires requires。たとえば、[expr.prim.req] / 3の次の例:

Aは必要-発現はまた、で使用することができ、必要句下記のようなテンプレート引数にアドホック制約を書き込む方法として([TEMP]):

template<typename T>
  requires requires (T x) { x + x; }
    T add(T a, T b) { return a + b; }

最初のrequireはrequire-clauseを導入し、2番目はrequire-expressionを導入します

その2番目のrequiresキーワードが必要になる背後にある技術的な理由は何ですか?書き込みを許可できないのはなぜですか。

template<typename T>
  requires (T x) { x + x; }
    T add(T a, T b) { return a + b; }

(注:文法は答えないでくださいrequires


25
提案:「requires requireが必要なものはありますか?」もっと真剣に、私はそれが背後にある同じ理由であるという直感を持っていnoexcept(noexcept(...))ます。
クエンティン、

11
requires私の意見では、この2つは同音異義語です。それらは同じように見え、同じスペルで、同じにおいがしますが、本質的に異なります。修正を提案する場合は、そのうちの1つの名前を変更することをお勧めします。
YSC

5
@YSC- co_requires?(申し訳ありませんが、抵抗できませんでした)。
StoryTeller-Unslander Monica

122
狂気はどこで止まりますか?あなたが知っている次のことは、我々が持っているでしょうlong long
エルジェイ

8
@StoryTeller: "Requires require required?" もっともっと説明的だっただろう!
PW

回答:


81

文法がそれを要求するからです。します。

requires制約はありませんする必要が使用してrequires式を。多かれ少なかれ任意のブール定数式を使用できます。したがって、requires (foo)正当なrequires制約でなければなりません。

requires 表現の異なる構造である(特定の物事が特定の制約に従うかどうかのテストがあることその事)。同じキーワードで導入されただけです。requires (foo f)有効なrequires式の始まりです。

必要なのは、requires制約を受け入れる場所で使用する場合、requires節から「制約+式」を作成できることです。

だからここに質問があります:requires (foo)require制約に適した場所に置いた場合...パーサーはこれが制約+式ではなくrequire 制約であることをあなたが望む方法であると認識する前にどれだけ遠くに行かなければなりませんかすることが?

このことを考慮:

void bar() requires (foo)
{
  //stuff
}

fooがタイプである場合(foo)、はrequire式のパラメーターリストであり、のすべて{}は関数の本体ではなく、そのrequires式の本体です。それ以外の場合fooは、requires句の式です。

まあ、あなたはコンパイラfooが最初に何であるかを理解すべきだと言うことができます。しかし、C ++ 、トークンのシーケンスを解析する基本的な動作で、コンパイラがトークンを理解する前にそれらの識別子の意味を理解する必要がある場合は、本当にそれを好みません。はい、C ++は状況依存なので、これは起こります。しかし、委員会は可能な限りそれを避けることを好みます。

そう、それは文法です。


2
型はあるが名前はないパラメーターリストがあることは意味がありますか?
NathanOliver

3
@クエンティン:C ++の文法には文脈依存のケースが確かにあります。しかし、委員会は本当にそれを最小化しようとします、そして彼らは間違いなくそれを追加することを嫌います
Nicol Bolas

3
@RobertAndrzejuk:一連のテンプレート引数または関数パラメーターリストの後にrequires表示される場合は<>、requires節です。requires式が有効な場所に表示される場合、それはrequire-expressionです。これは、解析ツリーの内容ではなく、解析ツリーの構造によって決定できます(識別子の定義方法の詳細はツリーの内容になります)。
Nicol Bolas

6
@RobertAndrzejuk:もちろん、requires-expressionは別のキーワードを使用することもできます。しかし、キーワードは、C ++では莫大なコストがかかります。キーワードとなった識別子を使用したプログラムを破壊する可能性があるためです。コンセプトの提案はすでに二つのキーワードを導入しました:conceptrequires。2番目が文法上の問題がなく、ユーザーが直面する問題がほとんどなく両方のケースをカバーできる場合、3番目を導入しても無駄です。結局のところ、唯一の視覚的な問題は、キーワードがたまたま2回繰り返されていることです。
Nicol Bolas

3
@RobertAndrzejuk概念を書いたかのように包摂されないため、このような制約をインライン化することはとにかく悪い習慣です。したがって、このような使用頻度の低い推奨されていない機能の識別子を取得することはお勧めできません。
Rakete1111

60

状況はとまったく同じnoexcept(noexcept(...))です。確かに、これは良いことより悪いことのように聞こえますが、説明させてください。:)私たちはあなたがすでに知っていることから始めます:

C ++ 11には「noexcept-clauses」と「-expressions」がありnoexceptます。彼らは異なることをします。

  • - noexcept節は、「この関数は次の場合を除いてはならない...(何らかの条件)」関数の宣言を行い、ブール値のパラメーターを取り、宣言された関数の動作を変更します。

  • noexcept-式は、「コンパイラは、言うかどうかを教えてください(一部の表現)がnoexceptです。」それ自体がブール式です。プログラムの動作に「副作用」はありません。コンパイラーに「はい/いいえ」の質問に対する答えを求めているだけです。「この表現は例外ではないのですか?」

-式を- 句内にネストすることもできますが、通常はそのようにするのは悪いスタイルだと考えています。noexceptnoexcept

template<class T>
void incr(T t) noexcept(noexcept(++t));  // NOT SO HOT

noexcept-式を型特性にカプセル化することは、より良いスタイルと考えられています。

template<class T> inline constexpr bool is_nothrow_incrable_v =
    noexcept(++std::declval<T&>());  // BETTER, PART 1

template<class T>
void incr(T t) noexcept(is_nothrow_incrable_v<T>);  // BETTER, PART 2

C ++ 2aワーキングドラフトには、「- requires句」と「requires- 式」があります。彼らは異なることをします。

  • - requires句は、「この関数は...(ある条件)のときにオーバーロードの解決に参加する必要がある」と述べています。関数の宣言を行い、ブール値のパラメーターを取り、宣言された関数の動作を変更します。

  • requires-式は、「コンパイラは、言うかどうかを教えてください(表現のいくつかのセット)が整形式です。」それ自体がブール式です。プログラムの動作に「副作用」はありません。コンパイラーに「はい/いいえ」の質問に対する答えを求めているだけです。「この表現は整形式ですか?」

-式を- 句内にネストすることもできますが、通常はそのようにするのは悪いスタイルだと考えています。requiresrequires

template<class T>
void incr(T t) requires (requires(T t) { ++t; });  // NOT SO HOT

requires-式を型特性にカプセル化する方がスタイルが良いと考えられています...

template<class T> inline constexpr bool is_incrable_v =
    requires(T t) { ++t; };  // BETTER, PART 1

template<class T>
void incr(T t) requires is_incrable_v<T>;  // BETTER, PART 2

...または(C ++ 2aワーキングドラフト)コンセプト。

template<class T> concept Incrable =
    requires(T t) { ++t; };  // BETTER, PART 1

template<class T>
void incr(T t) requires Incrable<T>;  // BETTER, PART 2

1
私はこの議論を実際に購入しません。noexcept問題があるnoexcept(f())意味するかもしれませんどちらかの解釈f()我々は仕様を設定するために使用していることをブール値としてかどうかをチェックf()ですnoexceptrequires有効性をチェックする式はすでに{}s で導入されているため、このあいまいさはありません。その後の議論は基本的に「文法はそう言っている」です。
バリー

@Barry:このコメントを参照してください。表示されます{}オプションです。
Eric

1
@Eric {}はオプションではありません。それはそのコメントが示すものではありません。ただし、これは解析のあいまいさを示す優れたコメントです。おそらくそのコメントを(いくつかの説明を付けて)スタンドアロンの回答として受け入れるでしょう
Barry

1
requires is_nothrow_incrable_v<T>;する必要がありますrequires is_incrable_v<T>;
Ruslan

16

これについては、cppreferenceの概念ページで説明されていると思います。私は「数学」で説明できるので、なぜこれが次のようでなければならないのか:

コンセプトを定義したい場合は、次のようにします。

template<typename T>
concept Addable = requires (T x) { x + x; }; // requires-expression

その概念を使用する関数を宣言する場合は、次のようにします。

template<typename T> requires Addable<T> // requires-clause, not requires-expression
T add(T a, T b) { return a + b; }

ここで、コンセプトを個別に定義したくない場合は、何らかの置換を行うだけで済みます。このパーツrequires (T x) { x + x; };を取り、パーツを交換するAddable<T>と、次のものが得られます。

template<typename T> requires requires (T x) { x + x; }
T add(T a, T b) { return a + b; }

これはあなたが尋ねていることです。


4
質問が何を求めているのか、私は思いません。これは多かれ少なかれ文法を説明しています。

しかし、なぜtemplate<typename T> requires (T x) { x + x; }requireを句と式の両方にすることができないのですか?
NathanOliver

2
@NathanOliver:ある構成を別の構成として解釈するようコンパイラーに強制しているため。requires-as-制約句はありませんしなければならないことrequires-expression。これは、その使用方法の1つにすぎません。
Nicol Bolas

2
@TheQuantumPhysicist私のコメントで私が得ていたのは、この答えは構文を説明するだけです。実際の技術的な理由ではありませんrequires requires。彼らは文法に許可するものを追加できたかもしれtemplate<typename T> requires (T x) { x + x; }ませんが、そうしませんでした。バリーは彼らがそうしなかった理由を知りたがっています
NathanOliver

3
ここで本当に文法の曖昧さを探しているなら、わかりました、かみます。godbolt.org/z/i6n8kM template<class T> void f(T) requires requires(T (x)) { (void)x; };は、いずれかのrequireses を削除すると、別の意味になります。
Quuxplusone

12

Andrew Sutton(Conceptsの作者の1人、gccで実装した人)からのコメントがこの点で非常に役立つことがわかりまし

少し前まで、requires-expressions(2番目のrequireで導入されたフレーズ)はconstraint-expressions(最初のrequireで導入されたフレーズ)では許可されていませんでした。それは概念の定義にしか現れません。実際、これはまさにその主張が現れるその論文のセクションで提案されているものです。

しかし、2016年に、その制限を緩和する提案がありました[編集者注:P0266 ]。論文のセクション4のパラグラフ4の取り消し線に注意してください。そして、このように生まれたのは必要です。

実を言うと、GCCでその制限を実際に実装したことは一度もなかったので、それは常に可能でした。ウォルターがそれを発見し、それが有用であると感じた可能性があり、その論文につながったと思います。

私が執筆に敏感でなかったと誰もが思うことを2回必要としないように、それを簡略化できるかどうかを判断するために少し時間を費やしました。短い答え:いいえ。

問題は、テンプレートパラメータリストの後に導入する必要のある2つの文法構成があることです。非常に一般的には、制約式(などP && Q)と、場合によっては構文要件(などrequires (T a) { ... })です。それはrequire-expressionと呼ばれます。

最初の要件は、制約を導入します。2番目のrequireはrequire-expressionを導入します。それが文法の構成方法です。私はそれが全く混乱しないと思います。

ある時点で、これらを単一の要件に縮小しようとしました。残念ながら、それはいくつかの非常に難しい解析問題につながります。たとえば(、requiresが入れ子になった部分式またはパラメーターリストを示しているかどうかは簡単にはわかりません。私はそれらの構文の完全な曖昧さの解消があるとは信じていません(統一された初期化構文の根拠を参照してください。この問題もそこにあります)。

だからあなたは選択をします:makeは(今のように)式を導入するか、要件のパラメーター化されたリストを導入する必要があります。

ほとんどの場合(ほぼ100%の場合など)、requires-expression以外のものが必要なため、現在のアプローチを選択しました。そして、非常にまれなケースでは、アドホックな制約のrequire-expressionが必要だったので、単語を2回書いてもかまいません。これは、私がテンプレートの十分に適切な抽象化を開発していないことを示す明らかな指標です。(もし持っていたら、名前が付けられていたからです。)

requireにrequire-expressionを導入するように選択することもできました。実際にはすべての制約が次のように見えるため、実際にはそれはさらに悪いことです。

template<typename T>
  requires { requires Eq<T>; }
void f(T a, T b);

ここでは、2番目のrequireをネストされた要件と呼びます。式を評価します(requires-expressionのブロック内の他のコードは評価されません)。これは現状よりずっと悪いと思います。今、あなたは書くためにどこでも2回必要になります。

さらに多くのキーワードを使用することもできます。これはそれ自体が問題であり、自転車の脱落だけではありません。重複を避けるためにキーワードを「再配布」する方法があるかもしれませんが、私はその真剣な考えを与えていません。しかし、それでも問題の本質は変わりません。


-10

モノAには要件Bがあり、要件Bには要件Cがあると言っているからです。

AにはBが必要であり、次にBが必要です。

「requires」句自体に何かが必要です。

A(Bが必要(Cが必要))があります。

ええ。:)


4
しかし、他の回答によると、最初と2番目requiresは概念的に同じものではありません(1つは句で、1つは式です)。実際、私が正しく理解していれば、()in の2つのセットのrequires (requires (T x) { x + x; })意味は大きく異なります(外部はオプションであり、常にブールconstexprを含みます。内部はrequire式を導入するための必須の部分であり、実際の式を許可していません)。
Max Langhof、

2
@MaxLanghof要件が異なると言っていますか?:D
オービットでの軽さのレース
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.