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);
(f)(s);
}
- 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));
S x(int());
S y((int)a);
S z = int(a);
}
- 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),
istream_iterator<int>());
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)),
istream_iterator<int>());
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;
decltype(i) x2;
decltype(a->x) x3;
decltype((a->x)) x4 = x3;
—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; }
decltype(auto) look_up_a_string_2() { auto str = lookup1(); return(str); }
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)
&(C::f), operand&jest nadalC::f, czyż nie?