development

왜 요구가 필요한가?

big-blog 2020. 6. 8. 07:56
반응형

왜 요구가 필요한가?


C ++ 20 개념의 모퉁이 중 하나는 작성해야하는 특정 상황이 있다는 것 requires requires입니다. 예를 들어, [expr.prim.req] / 3 의이 예는 다음과 같습니다 .

A는 필요-표현 도 사용할 수 있습니다 -요구 조항 등 아래와 같은 템플릿 인수에 임시 제약을 작성하는 방법으로 ([임시]) :

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

첫 번째는 require -clause를 도입하고 두 번째는 require-expression을 도입합니다 .

두 번째 requires키워드 가 필요한 기술적 이유는 무엇입니까 ? 왜 우리는 글쓰기를 허용 할 수 없습니까?

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

(참고 : 문법에 답하지 마십시오 requires)


문법에 필요하기 때문입니다. 그렇습니다.

requires제약하지 않는 해야 할 용도 requires표현. 임의의 부울 상수 표현식을 사용할 수 있습니다. 따라서 requires (foo)합법적 인 requires제약 조건 이어야합니다 .

requires 표현 독특한 구조이다 (어떤 일 특정 제약 조건에 따라 여부를 테스트하는 것이 그 것); 동일한 키워드로 소개되었습니다. requires (foo f)유효한 requires표현 의 시작입니다 .

당신이 원하는 것은 requires제약 조건을 받아들이는 장소에서 사용한다면 , requires에서 "제약 + 표현"을 만들 수 있어야한다는 것 입니다.

그래서 여기에 질문이 있습니다 : 당신이 requires (foo)요구 제약 조건에 적합한 장소에 넣으면 파서가 얼마나 멀리 갈 필요가 있는지 당신이 원하는 방식으로 제약 조건 + 표현보다는 요구 제약 조건 임을 깨닫기 전에 되려고?

이걸 고려하세요:

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

foo유형 인 경우 (foo)필수 표현식의 매개 변수 목록이며의 모든 내용 {}은 함수의 본문이 아니라 해당 requires표현식 의 본문입니다 . 그렇지 않으면 절의 foo표현식입니다 requires.

글쎄, 당신은 컴파일러 foo가 먼저 무엇을 알아 내야한다고 말할 수 있습니다. 그러나 C ++ 은 토큰 시퀀스를 구문 분석하는 기본 동작에서 컴파일러가 토큰을 이해하기 전에 해당 식별자의 의미를 파악해야 할 때 실제로 그것을 좋아하지 않습니다. 예, C ++는 상황에 따라 달라 지므로 이런 일이 발생합니다. 그러나위원회는 가능한 한 피하는 것을 선호한다.

그래, 문법이야.


상황은와 정확히 유사합니다 noexcept(noexcept(...)). 물론 이것은 좋은 것보다 나쁜 것 같지만 설명하겠습니다. :) 우리는 당신이 이미 알고있는 것으로 시작할 것입니다 :

C ++ 11에는 " noexcept-clauses"및 "-expressions"가 noexcept있습니다. 그들은 다른 일을합니다.

  • noexcept-clause는 "이 기능은 말했다 때 ... noexcept해야한다 (일부 조건)." 함수 선언으로 진행하고 부울 매개 변수를 사용하여 선언 된 함수에서 동작 변경을 발생시킵니다.

  • noexcept-expression는 "컴파일러는 말한다 여부를 말해주십시오 (일부 표현) noexcept입니다." 그 자체가 부울 표현식입니다. 프로그램 동작에 "부작용"이 없습니다. 예 / 아니오 질문에 대한 답변을 컴파일러에 요청하는 것입니다. "이 표현은 예외가 아닌가?"

우리는 할 수 둥지 noexcept, 안쪽 -expression noexcept-clause을하지만, 우리는 일반적으로 그것을 그렇게 나쁜 스타일을 고려한다.

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

noexcept-expression을 type-trait 로 캡슐화하는 것이 더 나은 스타일로 간주됩니다 .

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-clauses"및 "-expressions"가 requires있습니다. 그들은 다른 일을합니다.

  • - requires절은 "이 함수 는 ... (일부 조건) 일 때 과부하 해결에 참여해야합니다. "라고 말합니다 . 함수 선언으로 진행하고 부울 매개 변수를 사용하여 선언 된 함수에서 동작 변경을 발생시킵니다.

  • - requires컴파일러는 "컴파일러, (일부 표현식 세트)가 제대로 구성되어 있는지 알려주십시오 ." 그 자체가 부울 표현식입니다. 프로그램 동작에 "부작용"이 없습니다. 예 / 아니오 질문에 대한 답변을 컴파일러에 요청하는 것입니다. "이 표현이 잘 구성되어 있습니까?"

We can nest a requires-expression inside a requires-clause, but we typically consider it bad style to do so.

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

It's considered better style to encapsulate the requires-expression in a type-trait...

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

...or in a (C++2a Working Draft) concept.

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

I think cppreference's concepts page explains this. I can explain with "math" so to say, why this must be like this:

If you want to define a concept, you do this:

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

If you want to declare a function that uses that concept, you do this:

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

Now if you don't want to define the concept separately, I guess all you have to do is some substitution. Take this part requires (T x) { x + x; }; and replace the Addable<T> part, and you'll get:

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

which is what you're asking about.


I found a comment from Andrew Sutton (one of the Concepts authors, who implemented it in gcc) to be quite helpful in this regard, so I thought I'd just quote it here in its near-entirety:

Not so long ago requires-expressions (the phrase introduced by the second requires) was not allowed in constraint-expressions (the phrase introduced by the first requires). It could only appear in concept definitions. In fact, this is exactly what is proposed in the section of that paper where that claim appears.

However, in 2016, there was a proposal to relax that restriction [Editor's note: P0266]. Note the strikethrough of paragraph 4 in section 4 of the paper. And thus was born requires requires.

To tell the truth, I had never actually implemented that restriction in GCC, so it had always been possible. I think that Walter may have discovered that and found it useful, leading to that paper.

Lest anybody think that I wasn't sensitive to writing requires twice, I did spend some time trying to determine if that could be simplified. Short answer: no.

The problem is that there are two grammatical constructs that need to introduced after a template parameter list: very commonly a constraint expression (like P && Q) and occasionally syntactic requirements (like requires (T a) { ... }). That's called a requires-expression.

The first requires introduces the constraint. The second requires introduces the requires-expression. That's just the way the grammar composes. I don't find it confusing at all.

I tried, at one point, to collapse these to a single requires. Unfortunately, that leads to some seriously difficult parsing problems. You can't easily tell, for example if a ( after the requires denotes a nested subexpression or a parameter-list. I don't believe that there is a perfect disambiguation of those syntaxes (see the rationale for uniform initialization syntax; this problem is there too).

So you make a choice: make requires introduce an expression (as it does now) or make it introduce a parameterized list of requirements.

I chose the current approach because most of the time (as in nearly 100% of the time), I want something other than a requires-expression. And in the exceedingly rare case I did want a requires-expression for ad hoc constraints, I really don't mind writing the word twice. It's a an obvious indicator that I haven't developed a sufficiently sound abstraction for the template. (Because if I had, it would have a name.)

I could have chosen to make the requires introduce a requires-expression. That's actually worse, because practically all of your constraints would start to look like this:

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

Here, the 2nd requires is called a nested-requirement; it evaluates its expression (other code in the block of the requires-expression is not evaluated). I think this is way worse than the status quo. Now, you get to write requires twice everywhere.

I could also have used more keywords. This is a problem in its own right---and it's not just bike shedding. There might be a way to "redistribute" keywords to avoid the duplication, but I haven't given that serious thought. But that doesn't really change the essence of the problem.


Because you are saying that a thing A has a requirement B, and the requirement B has a requirement C.

The thing A requires B which in turn requires C.

The "requires" clause itself requires something.

You have thing A (requiring B (requiring C)).

Meh. :)

참고URL : https://stackoverflow.com/questions/54200988/why-do-we-require-requires-requires

반응형