Neden talep gerektirir?


161

C ++ 20 kavramlarının köşelerinden biri, yazmanız gereken belirli durumlar olmasıdır requires requires. Örneğin, [expr.prim.req] / 3'teki bu örnek :

Bir requir-ifadesi , below-template argümanlarına ad hoc kısıtlamaları yazmanın bir yolu olarak bir needs -cümlesi ([temp]) de kullanılabilir:

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

Birincisi gerektirir-cümlesi , ikincisi gerektirir-ifadesi .

Bu ikinci requiresanahtar kelimeye ihtiyaç duymanın teknik nedeni nedir ? Neden sadece yazmaya izin veremiyoruz:

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

(Not: lütfen dilbilgisine cevap requiresvermeyin)


25
Öneri: "Gerektiren gerektiren bir şey var mı?". Daha da ciddisi, aynı nedenden ötürü bir önsezim var noexcept(noexcept(...)).
Quentin

11
İkisi requiresbenim görüşüme göre homonymler: aynı görünüyorlar, aynı büyüyü yapıyorlar, aynı kokuyorlar, ama özünde farklılar. Bir düzeltme önerirsem, bunlardan birini yeniden adlandırmanızı öneririm.
YSC

5
@YSC - co_requires? (Üzgünüm, dayanamadım).
StoryTeller - Unlander Monica

122
Delilik nerede duracak? Bildiğiniz bir sonraki şey, sahip olacağız long long.
Eljay

8
@StoryTeller: "Gereksinim gerekli mi?" daha alliteratif olurdu!
PW

Yanıtlar:


81

Çünkü dilbilgisi bunu gerektirir. Öyle.

Bir requireskısıtlamanın bir ifade kullanması gerekmezrequires . Herhangi bir az ya da çok keyfi boole sabit ifadesini kullanabilir. Bu nedenle, requires (foo)meşru bir requireskısıtlama olmalıdır .

Bir requires ifade (belirli şeylerin belirli kısıtlamalara uyup uymadığını test eden şey) ayrı bir yapıdır; aynı anahtar kelime tarafından tanıtıldı. requires (foo f)geçerli bir requiresifadenin başlangıcı olacaktır .

İstediğiniz şey, requireskısıtlamaları kabul eden bir yerde kullanırsanız , requiresmaddeden bir "kısıtlama + ifade" yapabilmenizdir .

Öyleyse soru şudur: requires (foo)Gereksinim kısıtlaması için uygun bir yere koyarsanız ... ayrıştırıcının, bunun istediğiniz şekilde bir kısıtlama + ifade yerine zorunlu bir kısıtlama gerektirdiğinin farkına varmadan önce ne kadar ilerlemesi gerektiği olmak?

Bunu düşün:

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

Eğer footürüdür, o zaman (foo)bir bir parametre listesi ifadesini gerektirir, ve her şey {}fonksiyonun gövdesi ama bu vücut değildir requiresifadesi. Aksi takdirde, foobir requirescümledeki bir ifadedir .

Derleyicinin fooilk önce ne olduğunu bulması gerektiğini söyleyebilirsiniz . Ama C ++ gerçekten jeton dizisi ayrıştırma temel hareket bu tanımlayıcılar ne anlama geldiğini derleyici rakam o jeton mantıklı önce gerektirir zaman böyle değildir. Evet, C ++ içeriğe duyarlıdır, bu olur. Ancak komite mümkün olan yerlerde bundan kaçınmayı tercih ediyor.

Yani evet, dilbilgisi.


2
Türlü, ancak adı olmayan bir parametre listesine sahip olmak anlamlı mı?
NathanOliver

3
@Quentin: C ++ dilbilgisinde kesinlikle bağlama bağımlı durumlar vardır. Ancak komite bunu gerçekten en aza indirmeye çalışıyor ve kesinlikle daha fazlasını eklemekten hoşlanmıyorlar .
Nicol Bolas

3
@RobertAndrzejuk: requiresBir <>dizi şablon bağımsız değişkeninden sonra veya bir işlev parametre listesinden sonra görünürse , bu bir koşul-cümlesidir. Bir requiresifadenin geçerli olduğu yerde görünürse, bir zorunlu ifadedir. Bu, ayrıştırma ağacının içeriği ile değil, ayrıştırma ağacının yapısı ile belirlenebilir (bir tanımlayıcının nasıl tanımlandığının özellikleri ağacın içeriği olacaktır).
Nicol Bolas

6
@RobertAndrzejuk: Tabii, needs-expression farklı bir anahtar kelime kullanmış olabilir. Ancak, anahtar kelimeler C ++ 'da, anahtar kelime haline gelen tanımlayıcıyı kullanan herhangi bir programı kırma potansiyeline sahip olduklarından büyük maliyetlere sahiptir. Kavram önerisi zaten iki anahtar kelime getirdi: conceptve requires. Üçüncünün tanıtımı, ikincisinin dilbilgisi sorunu olmayan ve kullanıcılarla ilgili çok az sorunu olan her iki durumu da kapsayabileceği zaman, savurganlıktır. Sonuçta, tek görsel sorun, anahtar kelimenin iki kez tekrarlanmasıdır.
Nicol Bolas

3
@RobertAndrzejuk Bu şekilde satır içi kısıtlamalar uygulamak zaten kötü bir uygulamadır çünkü bir kavram yazmışsınız gibi toplamayı almazsınız. Bu nedenle, bu kadar düşük kullanım önerilmeyen bir özellik için bir tanımlayıcı almak iyi bir fikir değildir.
Rakete1111

60

Durum tam olarak benzer noexcept(noexcept(...)). Tabii, bu iyi bir şeyden çok kötü bir şey gibi geliyor, ama açıklamama izin verin. :) Bildiklerinizle başlayacağız:

C ++ 11, " noexcept-clauses" ve "-expressions" ifadelerine sahiptir noexcept. Farklı şeyler yaparlar.

  • A- noexceptclause der ki, "Bu işlev ne zaman istisnasız olmalıdır ... (bazı koşullar)." Bir işlev bildirimine devam eder, bir boolean parametresi alır ve bildirilen işlevde davranış değişikliğine neden olur.

  • Bir ifade noexcept, "Derleyici, lütfen bana (bazı ifadelerin) istisna olmadığını söyle ." Kendisi bir boole ifadesidir. Programın davranışı üzerinde hiçbir "yan etkisi" yoktur - sadece derleyiciden evet / hayır sorunun cevabını sormaktadır. "Bu ifade istisna değil mi?"

Biz can yuva bir noexceptbir içeriye -Anlatım noexcept-clause, ama biz genellikle o bunu yapmak için kötü bir stil düşünün.

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

noexcept-İfadesini bir tür özelliğinde kapsüllemek için daha iyi bir stil olarak kabul edilir .

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 Çalışma Taslağı " requires-clauses" ve "-expressions" ifadelerine sahiptir requires. Farklı şeyler yaparlar.

  • A requires-clause, "Bu fonksiyon şu durumlarda aşırı yük çözünürlüğüne katılmalıdır ... " Bir işlev bildirimine devam eder, bir boolean parametresi alır ve bildirilen işlevde davranış değişikliğine neden olur.

  • Bir ifade requires, "Derleyici, lütfen bana (bazı ifade kümelerinin) iyi biçimlendirilmiş olup olmadığını söyleyin ." Kendisi bir boole ifadesidir. Programın davranışı üzerinde hiçbir "yan etkisi" yoktur - sadece derleyiciden evet / hayır sorunun cevabını sormaktadır. "Bu ifade iyi biçimlendirilmiş mi?"

Biz can yuva bir requiresbir içeriye -Anlatım requires-clause, ama biz genellikle o bunu yapmak için kötü bir stil düşünün.

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

requiresTip ifadesinde-ifadesini kapsüllemek için daha iyi bir stil olarak kabul edilir ...

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

... veya (C ++ 2a Çalışma Taslağı) konseptinde.

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
Bu argümanı gerçekten satın almıyorum. noexceptsorunu var noexcept(f())anlamına gelebilir ya yorumlamak f()biz şartname ayarlamak için kullanmakta olduğunuz boolean olarak veya olup olmadığını kontrol f()olduğunu noexcept. requiresbu belirsizliğe sahip değildir, çünkü geçerliliğini kontrol ettiği ifadelerin {}s ile zaten girilmiş olması gerekir . Bundan sonra, argüman temel olarak "dilbilgisi böyle söylüyor".
Barry

@Barry: Bu yoruma bakın . Görünüşe {}göre isteğe bağlıdır.
Eric

1
@Eric {}İsteğe bağlı değildir, bu yorumun gösterdiği şey değildir. Ancak, bu ayrıştırma belirsizliğini gösteren harika bir yorumdur. Muhtemelen bu yorumu (bazı açıklamalarla birlikte) bağımsız bir cevap olarak kabul
Barry

1
requires is_nothrow_incrable_v<T>;olmalırequires is_incrable_v<T>;
Ruslan

16

Bence cppreference'ın kavramlar sayfası bunu açıklıyor. "Matematik" ile açıklayabilir miyim, neden böyle olması gerektiğini söylemek için:

Bir kavram tanımlamak istiyorsanız, bunu yapın:

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

Bu kavramı kullanan bir işlevi bildirmek istiyorsanız, bunu yaparsınız:

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

Şimdi kavramı ayrı ayrı tanımlamak istemiyorsanız, tek yapmanız gereken bir ikame. Bu yer alın requires (T x) { x + x; };ve değiştirme Addable<T>bölümünü ve elde edersiniz:

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

sormak istediğin şey bu.


4
Sorunun ne istediğini sanmıyorum. Bu dilbilgisini aşağı yukarı açıklıyor.
Passer By

Ama neden template<typename T> requires (T x) { x + x; }bu şartı hem ifade hem de ifade olabilir ve isteyemezsin?
NathanOliver

2
@NathanOliver: Çünkü derleyiciyi bir yapıyı diğeri olarak yorumlamaya zorluyorsunuz. Bir requiresgibi--kısıtlama hükmü vermez zorunda bir olmak requires-Anlatım. Bu sadece bir olası kullanımı.
Nicol Bolas

2
@TheQuantumPhysicist Benim yorum ile ne başlamıştı bu cevap sadece sözdizimini açıklıyor. Gerçek teknik nedenimiz yok requires requires. İzin vermek için dilbilgisine bir şeyler ekleyebilirlerdi, template<typename T> requires (T x) { x + x; }ama etmediler. Barry neden yapmadıklarını bilmek istiyor
NathanOliver

3
Burada gerçekten dilbilgisi-belirsizliği oynarsak, tamam, ısırırım. godbolt.org/z/i6n8kM template<class T> void f(T) requires requires(T (x)) { (void)x; }; , eslerden birini kaldırırsanız farklı bir şey ifade eder requires.
Quuxplusone

12

Andrew Sutton'ın (gcc'de uygulayan Concepts yazarlarından biri) bu konuda oldukça yardımcı olması için bir yorum buldum , bu yüzden burada tam olarak alıntı yapacağımı düşündüm:

Çok uzun zaman önce, kısıt ifadelerinde (ilkin gerektirdiği ifade) zorunlu-ifadelere (ikincinin gerektirdiği ifadeye) izin verilmedi. Yalnızca kavram tanımlarında görünebilir. Aslında, tam olarak bu iddianın ortaya çıktığı bölümde önerilen şey budur.

Ancak, 2016 yılında bu kısıtlamanın hafifletilmesi için bir teklif vardı [Editörün notu: P0266 ]. Makalenin 4. bölümündeki 4. paragrafın üstü çizili olduğuna dikkat edin. Ve böylece doğar gerektirir gerektirir.

Doğruyu söylemek gerekirse, bu kısıtlamayı hiçbir zaman GCC'de uygulamamıştım, bu yüzden her zaman mümkün olmuştu. Walter'ın bunu keşfetmiş ve faydalı bulmuş olabileceğini düşünüyorum.

Kimse yazmaya karşı duyarlı olmadığımı iki kere gerektirdiğini düşünse de, bunun basitleştirilip basitleştirilemeyeceğini belirlemek için biraz zaman harcadım. Kısa cevap: hayır.

Sorun, bir şablon parametre listesinden sonra sokulması gereken iki dilbilgisi yapısı olmasıdır: çok yaygın olarak bir kısıt ifadesi (gibi P && Q) ve bazen de sözdizimsel gereksinimler (gibi requires (T a) { ... }). Buna gereksinim ifadesi denir.

Birincisi kısıtlamayı getirir. İkincisi, gerektirir-ifadesini sunar. Sadece dilbilgisi bu şekilde oluşturulur. Hiç kafa karıştırıcı bulmuyorum.

Bir noktada bunları tek bir çöküşe uğraşmaya çalıştım. Ne yazık ki, bu bazı ciddi zor ayrıştırma sorunlarına yol açar. Kolayca söyleyemezsiniz, örneğin (bir sonrakinin iç içe geçmiş bir alt ifadeyi veya bir parametre listesini gösterip göstermediğini. Bu sözdizimlerinin mükemmel bir anlamsızlık olduğuna inanmıyorum (tekdüze başlatma sözdiziminin gerekçesine bakın; bu sorun da var).

Yani bir seçim yaparsınız: make, bir ifadeyi (şimdi olduğu gibi) tanıtmayı veya parametreli bir gereksinimler listesi sunmasını gerektirir.

Mevcut yaklaşımı seçtim çünkü çoğu zaman (neredeyse% 100'ünde olduğu gibi), zorunlu ifade dışında bir şey istiyorum. Ve son derece nadir durumlarda, geçici kısıtlamalar için bir gereksinim ifadesi istemiştim, kelimeyi iki kez yazmayı gerçekten umursamıyorum. Şablon için yeterince sağlam bir soyutlama geliştirmediğimin açık bir göstergesi. (Çünkü sahip olsaydım, bir ismi olurdu.)

Ben gerektirir gerektirir-ifade tanıtmak yapmak için seçmiş olabilir. Bu aslında daha kötü, çünkü neredeyse tüm kısıtlamalarınız şöyle görünmeye başlayacaktı:

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

Burada, ikinci gereksinime iç içe gereksinim denir; ifadesini değerlendirir (gerektiren ifadenin bloğundaki diğer kod değerlendirilmez). Bunun statükodan çok daha kötü olduğunu düşünüyorum. Şimdi, her yerde iki kez yazma gerektirir.

Daha fazla anahtar kelime de kullanabilirdim. Bu kendi başına bir problem --- ve sadece bisiklet atma değil. Yinelenmeyi önlemek için anahtar kelimeleri yeniden dağıtmanın bir yolu olabilir, ancak bu ciddi düşünceyi vermedim. Ancak bu, sorunun özünü gerçekten değiştirmez.


-10

Çünkü diyorsun ki A şeyinin B gereksinimi vardır ve B gereksiniminin C gereksinimi vardır.

A şey B gerektirir, bu da C gerektirir.

"Zorunlu" deyiminin kendisi bir şey gerektirir.

A şeyiniz var (B gerektiren (C gerektiren)).

Meh. :)


4
Ancak diğer cevaplara göre, birinci ve ikinci requireskavramsal olarak aynı şey değildir (biri bir cümle, bir ifade). Eğer doğru anlamak Aslında, iki set ()de requires (requires (T x) { x + x; })çok farklı anlamlara sahiptirler, (bir Boole constexpr içeren her zaman dış ve opsiyonel ve C iç bir ifade gerektirir sokulması zorunlu bir parçası olan ve olmayan gerçek ifadeleri izin verir).
Max Langhof

2
@MaxLanghof Gereksinimlerin farklı olduğunu mu söylüyorsunuz? : D
Yörüngedeki Hafiflik Yarışları
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.