Kiedy dodatkowe nawiasy mają wpływ, inny niż pierwszeństwo operatorów?


91

Nawiasy w C ++ są używane w wielu miejscach: np. W wywołaniach funkcji i wyrażeniach grupujących, aby przesłonić pierwszeństwo operatorów. Oprócz niedozwolonych dodatkowych nawiasów (takich jak listy argumentów wywołań funkcji), ogólna - ale nie bezwzględna - zasada C ++ mówi, że dodatkowe nawiasy nigdy nie zaszkodzą :

5.1 Wyrażenia podstawowe [wyr.prim]

5.1.1 Ogólne [wyr.prim.general]

6 Wyrażenie w nawiasach jest wyrażeniem podstawowym, którego typ i wartość są identyczne z wyrażeniem zawartym w nim. Obecność nawiasów nie wpływa na to, czy wyrażenie jest lwartością. Wyrażenia ujętego w nawiasy można używać w dokładnie takich samych kontekstach, jak te, w których można użyć wyrażenia ujętego w nawiasach, io tym samym znaczeniu, chyba że wskazano inaczej .

Pytanie : w jakich kontekstach dodatkowe nawiasy zmieniają znaczenie programu C ++, inne niż przesłanianie podstawowego pierwszeństwa operatorów?

UWAGA : Uważam, że ograniczenie składni wskaźnika do elementu członkowskiego do &qualified-idbez nawiasów jest poza zakresem, ponieważ ogranicza składnię, a nie zezwala na dwie składnie o różnych znaczeniach. Podobnie, użycie nawiasów w definicjach makr preprocesora chroni również przed niepożądanym pierwszeństwem operatorów.


„Uważam, że rozwiązanie & (identyfikator-kwalifikowany) do wskaźnika do elementu członkowskiego jest zastosowaniem pierwszeństwa operatora”. -- Dlaczego? Jeśli pominiesz nawiasy w &(C::f), operand &jest nadal C::f, czyż nie?

@hvd expr.unary.op/4: wskaźnik do elementu członkowskiego jest tworzony tylko wtedy, gdy &używany jest jawny, a jego operand jest kwalifikowanym identyfikatorem nieuwzględnionym w nawiasach.
TemplateRex,

Więc co to ma wspólnego z pierwszeństwem operatorów? (Nieważne, twoje zredagowane pytanie

@hvd zaktualizowane, myliłem RHS z LHS w tym pytaniu i odpowiedziach , a tam parens są używane do nadpisania pierwszeństwa wywołania funkcji ()nad selektorem wskaźnika do elementu członkowskiego::*
TemplateRex

1
Myślę, że powinieneś bardziej precyzyjnie określić, które przypadki wymagają rozważenia. Na przykład nawiasy wokół nazwy typu, aby uczynić go operatorem rzutowania w stylu C (niezależnie od kontekstu), w ogóle nie tworzą wyrażenia w nawiasach. Z drugiej strony, technicznie rzecz biorąc, powiedziałbym, że warunek po if lub while jest wyrażeniem w nawiasach, ale ponieważ nawiasy są częścią składni tutaj, nie powinny być brane pod uwagę. Nie powinno tak być też w przypadku IMO, gdzie bez nawiasów wyrażenie nie byłoby już analizowane jako pojedyncza jednostka, niezależnie od tego, czy występuje pierwszeństwo operatora, czy nie.
Marc van Leeuwen

Odpowiedzi:


113

TL; DR

Dodatkowe nawiasy zmieniają znaczenie programu C ++ w następujących kontekstach:

  • zapobieganie wyszukiwaniu nazw zależnych od argumentów
  • włączenie operatora przecinka w kontekstach listowych
  • rozwiązanie niejednoznaczności irytujących analiz
  • wnioskowanie odwołań w decltypewyrażeniach
  • zapobieganie błędom makr preprocesora

Zapobieganie wyszukiwaniu nazw zależnych od argumentów

Jak opisano szczegółowo w załączniku A do normy, a post-fix expressionw formularzu (expression)jest a primary expression, ale nie id-expression, a zatem nie unqualified-id. Oznacza to, że wyszukiwanie nazw zależnych od argumentów jest niemożliwe w wywołaniach funkcji formularza w (fun)(arg)porównaniu z formą konwencjonalną fun(arg).

3.4.2 Wyszukiwanie nazw zależnych od argumentów [basic.lookup.argdep]

1 Gdy wyrażenie postfix w wywołaniu funkcji (5.2.2) jest niekwalifikowanym identyfikatorem , można przeszukiwać inne przestrzenie nazw nieuwzględnione podczas zwykłego wyszukiwania bez kwalifikacji (3.4.1), aw tych przestrzeniach nazw, funkcja zaprzyjaźniona z zakresem przestrzeni nazw lub można znaleźć deklaracje szablonów funkcji (11.3) niewidoczne w innym przypadku. Te modyfikacje wyszukiwania zależą od typów argumentów (aw przypadku argumentów szablonu szablonu, przestrzeni nazw argumentu szablonu). [Przykład:

namespace N {
    struct S { };
    void f(S);
}

void g() {
    N::S s;
    f(s);   // OK: calls N::f
    (f)(s); // error: N::f not considered; parentheses
            // prevent argument-dependent lookup
}

- koniec przykładu]

Włączanie operatora przecinka w kontekstach list

Operator przecinka ma specjalne znaczenie w większości kontekstów listowych (argumenty funkcji i szablonów, listy inicjalizujące itp.). Nawiasy formularza a, (b, c), dw takich kontekstach mogą umożliwić użycie operatora przecinka w porównaniu do zwykłego formularza, w a, b, c, dktórym operator przecinka nie ma zastosowania.

5.18 Operator przecinka [wyr.comma]

2 W kontekstach, w których przecinkowi nadaje się specjalne znaczenie, [Przykład: na listach argumentów funkcji (5.2.2) i listach inicjatorów (8.5) - przykład na końcu] operator przecinka, jak opisano w Rozdziale 5, może występować tylko w nawiasach. [Przykład:

f(a, (t=3, t+2), c);

ma trzy argumenty, z których drugi ma wartość 5. - koniec przykładu]

Rozwiązanie niejednoznaczności irytujących analiz

Kompatybilność wsteczna z C i jego tajemną składnią deklaracji funkcji może prowadzić do zaskakujących niejednoznaczności analizowania, znanych jako irytujące analizy. Zasadniczo wszystko , co można przeanalizować jako deklarację, zostanie przeanalizowane jako jedna , nawet jeśli konkurencyjna analiza również miałaby zastosowanie.

6.8 Rozwiązywanie niejednoznaczności [stmt.ambig]

1 W gramatyce występuje niejednoznaczność dotycząca instrukcji wyrażeń i deklaracji : instrukcja wyrażenia z jawną konwersją typu w stylu funkcji (5.2.3) jako skrajnym lewym podwyrażeniem może być nie do odróżnienia od deklaracji, w której pierwszy deklarator zaczyna się od ( . W tych przypadkach oświadczenie jest oświadczeniem .

8.2 Rozwiązywanie niejednoznaczności [dcl.ambig.res]

1 Niejednoznaczność wynikająca z podobieństwa między rzutowaniem w stylu funkcji a deklaracją wspomnianą w 6.8 może również wystąpić w kontekście deklaracji . W tym kontekście wybór następuje między deklaracją funkcji z nadmiarowym zestawem nawiasów wokół nazwy parametru a deklaracją obiektu z rzutowaniem w stylu funkcji jako inicjatorem. Podobnie jak w przypadku niejasności wspomnianych w 6.8, rezolucja polega na uznaniu dowolnego konstruktu, który mógłby być deklaracją, za deklarację . [Uwaga: Deklaracja może być jawnie rozróżniona przez rzutowanie w stylu niefunkcjonalnym, przez = w celu wskazania inicjalizacji lub przez usunięcie zbędnych nawiasów wokół nazwy parametru. —End note] [Przykład:

struct S {
    S(int);
};

void foo(double a) {
    S w(int(a));  // function declaration
    S x(int());   // function declaration
    S y((int)a);  // object declaration
    S z = int(a); // object declaration
}

- koniec przykładu]

Słynnym tego przykładem jest The Most Vexing Parse , nazwa spopularyzowana przez Scotta Meyersa w pozycji 6 jego książki Effective STL :

ifstream dataFile("ints.dat");
list<int> data(istream_iterator<int>(dataFile), // warning! this doesn't do
               istream_iterator<int>());        // what you think it does

To deklaruje funkcję data, której typem zwracanym jest list<int>. Dane funkcji mają dwa parametry:

  • Nazwano pierwszy parametr dataFile. To jest typ istream_iterator<int>. Nawiasy wokół dataFilesą zbędne i są ignorowane.
  • Drugi parametr nie ma nazwy. Jego typ to wskaźnik do funkcji nie pobierającej niczego i zwracającej istream_iterator<int>.

Umieszczenie dodatkowych nawiasów wokół pierwszego argumentu funkcji (nawiasy wokół drugiego argumentu są niedozwolone) rozwiąże niejednoznaczność

list<int> data((istream_iterator<int>(dataFile)), // note new parens
                istream_iterator<int>());          // around first argument
                                                  // to list's constructor

C ++ 11 ma składnię inicjującą nawiasy klamrowe, która pozwala na ominięcie takich problemów parsowania w wielu kontekstach.

Wyprowadzanie odwołań w decltypewyrażeniach

W przeciwieństwie do autodedukcji typu, decltypeumożliwia wywnioskowanie referencyjności (odniesień do lwartości i rwartości). Reguły rozróżniają decltype(e)i decltype((e))wyrażenia:

7.1.6.2 Proste specyfikatory typu [dcl.type.simple]

4 W przypadku ekspresji e, typ oznaczonadecltype(e) jest zdefiniowany w następujący sposób:

- if ejest wyrażeniem id bez rodzicielstwa lub dostępem do elementu członkowskiego klasy bez rodzicielstwa (5.2.5), decltype(e)jest typem jednostki nazwanej przez e. Jeśli nie ma takiej encji lub jeśli enazywa zbiór przeciążonych funkcji, program jest źle sformułowany;

- w przeciwnym razie, jeśli ejest wartością x, decltype(e)to jest T&&, gdzie Tjest typem e;

- w przeciwnym razie, jeśli ejest lwartością, decltype(e)jest T&, gdzie Tjest typem e;

- w przeciwnym razie decltype(e)jest to typ e.

Operand specyfikatora decltype jest operandem niedoszacowanym (klauzula 5). [Przykład:

const int&& foo();
int i;
struct A { double x; };
const A* a = new A();
decltype(foo()) x1 = 0;   // type is const int&&
decltype(i) x2;           // type is int
decltype(a->x) x3;        // type is double
decltype((a->x)) x4 = x3; // type is const double&

—Końcowy przykład] [Uwaga: Zasady określania typów obejmujących decltype(auto)są określone w 7.1.6.4. —End note]

Reguły dla decltype(auto)mają podobne znaczenie dla dodatkowych nawiasów po prawej stronie wyrażenia inicjującego. Oto przykład z często zadawanych pytań dotyczących języka C ++ oraz powiązanych pytań i odpowiedzi

decltype(auto) look_up_a_string_1() { auto str = lookup1(); return str; }  //A
decltype(auto) look_up_a_string_2() { auto str = lookup1(); return(str); } //B

Pierwszy zwraca string, drugi zwraca string &, czyli odwołanie do zmiennej lokalnej str.

Zapobieganie błędom związanym z makrami preprocesora

Istnieje wiele subtelności z makrami preprocesora w ich interakcji z właściwym językiem C ++, z których najczęstsze są wymienione poniżej

  • używanie nawiasów wokół parametrów makr wewnątrz definicji makra #define TIMES(A, B) (A) * (B);w celu uniknięcia niepożądanego pierwszeństwa operatora (np. w TIMES(1 + 2, 2 + 1)którym daje 9, ale daje 6 bez nawiasów wokół (A)i(B)
  • używanie nawiasów wokół argumentów makr zawierających przecinki: assert((std::is_same<int, int>::value));które w przeciwnym razie nie zostałyby skompilowane
  • używanie nawiasów wokół funkcji w celu ochrony przed rozszerzaniem makr w dołączonych nagłówkach: (min)(a, b)(z niepożądanym efektem ubocznym wyłączania ADL)

7
Tak naprawdę nie zmienia znaczenia programu, ale najlepsza praktyka i wpływa na ostrzeżenia emitowane przez kompilator: dodatkowe nawiasy powinny być używane w if/, whilejeśli wyrażenie jest przypisaniem. Np. if (a = b)- ostrzeżenie (czy miałeś na myśli ==?), Podczas gdy if ((a = b))- bez ostrzeżenia.
Csq

@Csq dzięki, dobra obserwacja, ale jest to ostrzeżenie ze strony konkretnego kompilatora i nie jest wymagane przez Standard. Nie sądzę, żeby pasowało to do językowo-prawniczego charakteru tego pytania i odpowiedzi.
TemplateRex,

Czy (min)(a, b)(ze złym MACRO min(A, B)) jest częścią zapobiegania wyszukiwaniu nazw zależnym od argumentów?
Jarod42

@ Jarod42 Chyba tak, ale rozważmy, że takie i inne złe makra są poza zakresem pytania :-)
TemplateRex,

5
@JamesKanze: Zwróć uwagę, że OP i TemplateRex to ta sama osoba ^ _ ^
Jarod42,

4

Ogólnie rzecz biorąc, w językach programowania „dodatkowe” nawiasy oznaczają, że nie zmieniają one kolejności składniowej analizy ani znaczenia. Są dodawane w celu wyjaśnienia kolejności (pierwszeństwa operatorów) z korzyścią dla osób czytających kod, a ich jedynym efektem byłoby nieznaczne spowolnienie procesu kompilacji i zmniejszenie liczby błędów ludzkich w zrozumieniu kodu (prawdopodobnie przyspieszenie całego procesu tworzenia ).

Jeśli zestaw nawiasów faktycznie zmienia sposób przetwarzania wyrażenia, to z definicji nieone dodatkowymi. Nawiasy, które zamieniają niedozwoloną / nieprawidłową analizę na legalną, nie są „ekstra”, chociaż może to wskazywać na kiepski projekt języka.


2
dokładnie i jest to ogólna zasada również w C ++ (zobacz standardowy cytat w pytaniu), chyba że wskazano inaczej . Wskazanie na te „słabości” było celem tego pytania i odpowiedzi.
TemplateRex,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.