1<!--===- docs/C++17.md 2 3 Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 See https://llvm.org/LICENSE.txt for license information. 5 SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 7--> 8 9## C++14/17 features used in f18 10 11The C++ dialect used in this project constitutes a subset of the 12standard C++ programming language and library features. 13We want our dialect to be compatible with the LLVM C++ language 14subset that will be in use at the time that we integrate with that 15project. 16We also want to maximize portability, future-proofing, 17compile-time error checking, and use of best practices. 18 19To that end, we have a C++ style guide (q.v.) that lays 20out the details of how our C++ code should look and gives 21guidance about feature usage. 22 23We have chosen to use some features of the recent C++17 24language standard in f18. 25The most important of these are: 26* sum types (discriminated unions) in the form of `std::variant` 27* `using` template parameter packs 28* generic lambdas with `auto` argument types 29* product types in the form of `std::tuple` 30* `std::optional` 31 32(`std::tuple` is actually a C++11 feature, but I include it 33in this list because it's not particularly well known.) 34 35### Sum types 36 37First, some background information to explain the need for sum types 38in f18. 39 40Fortran is notoriously problematic to lex and parse, as tokenization 41depends on the state of the partial parse; 42the language has no reserved words in the sense that C++ does. 43Fortran parsers implemented with distinct lexing and parsing phases 44(generated by hand or with tools) need to implement them as 45coroutines with complicated state, and experience has shown that 46it's hard to get them right and harder to extend them as the language 47evolves. 48 49Alternatively, with the use of backtracking, one can parse Fortran with 50a unified lexer/parser. 51We have chosen to do so because it is simpler and should reduce 52both initial bugs and long-term maintenance. 53 54Specifically, f18's parser uses the technique of recursive descent with 55backtracking. 56It is constructed as the incremental composition of pure parsing functions 57that each, when given a context (location in the input stream plus some state), 58either _succeeds_ or _fails_ to recognize some piece of Fortran. 59On success, they return a new state and some semantic value, and this is 60usually an instance of a C++ `struct` type that encodes the semantic 61content of a production in the Fortran grammar. 62 63This technique allows us to specify both the Fortran grammar and the 64representation of successfully parsed programs with C++ code 65whose functions and data structures correspond closely to the productions 66of Fortran. 67 68The specification of Fortran uses a form of BNF with alternatives, 69optional elements, sequences, and lists. Each of these constructs 70in the Fortran grammar maps directly in the f18 parser to both 71the means of combining other parsers as alternatives, &c., and to 72the declarations of the parse tree data structures that represent 73the results of successful parses. 74Move semantics are used in the parsing functions to acquire and 75combine the results of sub-parses into the result of a larger 76parse. 77 78To represent nodes in the Fortran parse tree, we need a means of 79handling sum types for productions that have multiple alternatives. 80The bounded polymorphism supplied by the C++17 `std::variant` fits 81those needs exactly. 82For example, production R502 in Fortran defines the top-level 83program unit of Fortran as being a function, subroutine, module, &c. 84The `struct ProgramUnit` in the f18 parse tree header file 85represents each program unit with a member that is a `std::variant` 86over the six possibilities. 87Similarly, the parser for that type in the f18 grammar has six alternatives, 88each of which constructs an instance of `ProgramUnit` upon the result of 89parsing a `Module`, `FunctionSubprogram`, and so on. 90 91Code that performs semantic analysis on the result of a successful 92parse is typically implemented with overloaded functions. 93A function instantiated on `ProgramUnit` will use `std::visit` to 94identify the right alternative and perform the right actions. 95The call to `std::visit` must pass a visitor that can handle all 96of the possibilities, and f18 will fail to build if one is missing. 97 98Were we unable to use `std::variant` directly, we would likely 99have chosen to implement a local `SumType` replacement; in the 100absence of C++17's abilities of `using` a template parameter pack 101and allowing `auto` arguments in anonymous lambda functions, 102it would be less convenient to use. 103 104The other options for polymorphism in C++ at the level of C++11 105would be to: 106* loosen up compile-time type safety and use a unified parse tree node 107 representation with an enumeration type for an operator and generic 108 subtree pointers, or 109* define the sum types for the parse tree as abstract base classes from 110 which each particular alternative would derive, and then use virtual 111 functions (or the forbidden `dynamic_cast`) to identify alternatives 112 during analysis 113 114### Product types 115 116Many productions in the Fortran grammar describe a sequence of various 117sub-parses. 118For example, R504 defines the things that may appear in the "specification 119part" of a subprogram in the order in which they are allowed: `USE` 120statements, then `IMPORT` statements, and so on. 121 122The parse tree node that represents such a thing needs to incorporate 123the representations of those parses, of course. 124It turns out to be convenient to allow these data members to be anonymous 125components of a `std::tuple` product type. 126This type facilitates the automation of code that walks over all of the 127members in a type-safe fashion and avoids the need to invent and remember 128needless member names -- the components of a `std::tuple` instance can 129be identified and accessed in terms of their types, and those tend to be 130distinct. 131 132So we use `std::tuple` for such things. 133It has also been handy for template metaprogramming that needs to work 134with lists of types. 135 136### `std::optional` 137 138This simple little type is used wherever a value might or might not be 139present. 140It is especially useful for function results and 141rvalue reference arguments. 142It corresponds directly to the optional elements in the productions 143of the Fortran grammar. 144It is also used as a wrapper around a parse tree node type to define the 145results of the various parsing functions, where presence of a value 146signifies a successful recognition and absence denotes a failed parse. 147It is used in data structures in place of nullable pointers to 148avoid indirection as well as the possible confusion over whether a pointer 149is allowed to be null. 150