| Document Number: | P0056R00 | 
|---|---|
| Date: | 2015-09-12 | 
| Project: | Programming Language C++, EWG | 
| Revises: | none | 
| Reply to: | gorn@microsoft.com | 
Bikeshed alternatives: scoped keywords, std-qualified keywords, named keywords.
"We have to go with the odd ones, as all of the good ones are already taken". This was said a few times at different WG21 meetings. It was in a reference to keywords. C++ is a mature language with large existing codebases and an attempt to introduce a new keyword into the language will necessarily break existing code.
Quick sampling of some of the proposed keywords from Concepts, Modules, 
Transaction Memory, Pattern Matching, and Coroutines papers (N3449, 
N4134, 
N4361, 
N4466, 
N4513, 
[PatternMatch]) in private and public codebases reveals that identifiers 
await, concept, requires, 
synchronized, module, inspect, 
when are used as names of variables, fields, parameters, namespaces 
and classes.
This paper explores the idea of adding soft keywords to the C++ 
language. This will enable new language features to select best possible keyword 
names without breaking existing software. The idea is simple. Soft 
keyword is a named entity implicitly defined in std or 
std::experimental namespaces that participates in the name lookup 
and name hiding according to existing language rules. If a name lookup finds an 
implicit declaration of a soft keyword it is treated in the same way as 
other context-dependent keyword resolved by the name lookup such as 
typedef-name, namespace-name, class-name, 
etc.
In the example below yield is a soft keyword implicitly 
defined in the std namespace.
namespace N1 { void yield(int); }
auto coro2() {
  using std::yield;
  yield(2);           // yields value 2
  N1::yield(3);       // invokes N1::yield
} 
auto coro3() {
  using namespace N1;
  yield(1);           // invokes N1::yield
  std::yield(3);      // yields value 3
}
auto coro4() {
  using namespace N1;
  using namespace std;
  yield(4);           // error: ambiguous
}
Drawback of the simple model described in the introduction is that without a using declaration or using directive, the developer need to always use std:: with the soft keyword. This is troublesome as people would have to remember which keywords are the soft keywords and which are the good old "hard ones". This can be alleviated by adding a paragraph to the section 3.5 [basic.lookup] stating:
(5) if an unqualified name lookup [basic.lookup.unqual] or an argument-dependent name lookup [basic.lookup.argdep] fails to find a declaration and an identifier being looked up is a soft keyword identifier, it is treated as the corresponding context-dependent keyword
With this addition, we are getting to near perfect keyword 
experience. In the following example module is a soft keyword.
module A; // OK. Lookup did not find any
Xyz::Pcmf *module; // OK
bool FileHandleList::Find(LPCWSTR file)
{
    FileHandleCachePaths::iterator
        module = _Find(file); // OK
    return module != m_hInstPaths.end(); // OK
}
If a grammar construct utilizing a particular soft keyword can be interpreted as a function call when used in the template definition and being a dependent name, the current rules will result in the construct being treated as a function call. This preserves the property that a template can be correctly parsed prior to instantiation. That means that for some constructs, in templates, one must use explicitly qualified soft keywords, unless there a preceding using directive or declaration.
In the examples bellow, inspect and when are 
soft keywords.
template <typename T>
double area(const Shape& s, T u)
{
    inspect (s) { // OK: not a dependent name
      when Circle:    return 2*pi*radius();
      when Square:     return height()*width();
      default:        error(“unknown shape”);
    }
    std::inspect(u) { // must be qualified, otherwise will be parsed as a function call
      when X: return 1.0;
    }
}
Similarly, with yield soft keyword, in some cases, qualification 
will be needed.
template <typename T> 
auto g() {
  T v;
  T* p;
  yield v;       // yield expression (not a dependent name, not a function call expr)
  yield(5);      // yield expression (not a dependent name)
  std::yield(v); // yield expression (not a dependent name, since qualified) 
  std::yield *p; // yield expression (not a dependent name, since qualified)
  yield *p;      // operator * call, yield is not a soft keyword
  yield(v);      // function call, yield is a dependent name
}
This is unfortunate, but, developers are already trained to deal with two 
phase lookup in templates and take care of it, by inserting 
typename, template and this-> as 
needed. Soft keywords add one more annoyance they have to deal with, unless we 
can take advantage of modules.
However, situation is not as bleak as it may seem. Modules get us to perfect keyword experience, as they allow free use of using directives / declarations without exporting using directives / declarations outside of the module.
module A;
using namespace std;
template<typename T, typename U>
export void f(T& x, U xx)
{
    inspect (x,xx) { // OK: not a dependent name, as the lookup finds std::inspect
       when {int* p,0}:         p=nullptr;
       when {_a,int}:     …    // _a is a placeholder matching everything
                // shorthand for auto _a
    }
}
If someone finds using directive too broad, one can define a module with all of their favorite soft keywords exported in using declarations as follows:
module Cpp17keywords;
export {
  using std::inspect;
  using std::when;
  using std::await;
  using std::yield;
  ...
}
and now, any module can take advantage of using unqualified soft keywords by 
having import Cpp17keywords; declaration.
Yes. If a source file uses using namespace std and defines an 
entity with the name matching the soft keyword xyz in the global 
namespace or in another namespace X that is available for unqualified name 
lookup due to using namespace X, then, the lookup will be 
ambiguous. The fix would be to explicitly qualify the name in question with 
::xyz or X::xyz.
We can also do not break existing code, by altering paragraph 2 of section [namespace.udir] as follows (changes are in bold):
A using-directive specifies that the names in the nominated namespace can be used in the scope in which the
using-directive appears after the using-directive. During unqualified name lookup (3.4.1), the names appear
as if they were declared in the nearest enclosing namespace which contains both the using-directive and the
nominated namespace. This affects all names except the names of the soft keywords.
One may ask, why should we do this? We don't guard against introducing new 
library functions in std namespace, why should we do this for 
keywords? For functions, library can rely on overloading and SFINAE to remove 
function names from consideration and reduce the chance of collision. We don't 
have this ability for keywords. Nevertheless, authors feel ambivalent about this 
rule and would like committee guidance. 
What about tools? Would soft keywords confuse them? Not necessarily. If we introduce new constructs to the language tools need to be adapt to them.
Precise tools already have to rely on name lookup to figure out if X * 
y; is a declaration of a variable of type pointer to X or 
multiplication of X and y. Thus, they should be able 
to distinguish between identifiers and soft keywords.
Imprecise tools rely on heuristics to decide how to parse without having 
complete knowledge  of all the symbols. In that case, they would have to use 
heuristics depending on the construct. For example, if inspect(x) 
is followed by the {, then heuristic would be that 
inspect is a keyword, otherwise, assume function name.
A version of this proposal was implemented in non-shipping version of Microsoft C++ compiler.
Here is a very rough sketch of how the wording might look for soft keywords. 
As an illustration, I use the soft keywords yield and 
await. 
Add yield and await to the table 2 (Identifiers 
with special meaning).
In paragraph 3 add the text in bold.
An entity is a value, object, reference, function, enumerator, type,
class member, template, template specialization, namespace, parameter
pack, soft keyword, orthis.
Add the following paragraph after paragraph 4.
If an unqualified name lookup [basic.lookup.unqual] or an argument-dependent name lookup [basic.lookup.argdep] fails to find a declaration and an identifier being looked up is a soft keyword identifier, it is treated as corresponding context-dependent keyword
In paragraph 1, add the text in bold.
The name lookup rules apply uniformly to all names (including typedef-names (7.1.3), namespace-names (7.3), soft-keyword-names (3.12),
and class-names (9.1)) ...
Soft keywords yield and await are implicitly 
declared in the std::experimental namespace. In the grammar 
productions, yield-soft-keyword-name and 
await-soft-keyword-name represent context-dependent keywords 
resulted from the name lookup according to the rules in 3.4 [basic.lookup].
[Note: This is an illustration of soft keywords used in grammar production]
await-expression: await-soft-keyword-name 
cast-expression
N4134: 
Resumable Functions v2 (http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2014/n4134.pdf)
N4361: 
C++ extensions for Concepts (http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4361.pdf)
N3449: 
Open and Efficient Type Switch for C++ (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3449.pdf)
N4466: 
Wording for Modules (http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4466.pdf)
N4513: 
Working Draft Technical Specification for C++ Extensions for Transactional 
Memory (http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4513.pdf)
[PatternMatch]: 
Presentation from the evening session at Urbana 2014