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
decltype
wyraż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 expression
w 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), d
w takich kontekstach mogą umożliwić użycie operatora przecinka w porównaniu do zwykłego formularza, w a, b, c, d
któ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ół dataFile
są 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 decltype
wyrażeniach
W przeciwieństwie do auto
dedukcji typu, decltype
umoż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 e
jest 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 e
nazywa zbiór przeciążonych funkcji, program jest źle sformułowany;
- w przeciwnym razie, jeśli e
jest wartością x, decltype(e)
to jest T&&
, gdzie T
jest typem e
;
- w przeciwnym razie, jeśli e
jest lwartością, decltype(e)
jest T&
, gdzie T
jest 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?