P2958R1
typeof and typeof_unqual for C++

Published Proposal,

This version:
https://thephd.dev/_vendor/future_cxx/papers/d2958.html
Author:
Audience:
EWG
Project:
ISO/IEC 14882 Programming Languages — C++, ISO/IEC JTC1/SC22/WG21
Target:
C++26
Latest:
https://thephd.dev/_vendor/future_cxx/papers/d2958.html

Abstract

The long-anticipated type features from C have been integrated into C23, and should be ported to WG21 for harmony.

1. Revision History

1.1. Revision 1 - November 4th, 2023

1.2. Revision 0 - August 5th, 2023

2. Introduction and Motivation

In the oldest proposals for decltype(...), the proposal authors noted how the construct would produce a reference under common situations involving declarations and usage of C code inside of it. This was part of a sincere argument to leave typeof alone as a keyword and instead pivot to the decltype keyword instead, with some authors directly pointing out serious flaws with trying to take the typeof keyword from the existing vendor extension and C implementation space [n2343] [n1978].

The intention was that keyword differed enough from the existing practice from C and C++ compilers with the typeof and __typeof__ extensions that they should leave it alone, and let C standardize it at its own pace. C23 added typeof and, at the request of users working on the Linux Kernel and within the GCC bug tracker, typeof_unqual to solve the distinct issues with C [n2927]. Furthermore, it keeps with the existing practice that allows both expressions and type names to go into the typeof operator in a way that decltype chose not to.

Critically, this means that typeof and __typeof__ cannot be fully approximated with decltype alone, or even a macro such as #define typeof(...) ::std::remove_reference_t<decltype(__VA_ARGS__)>. C and C++ implementations the typeof extension can already take a type specifier (or, more specifically for the case of C++ grammar, a type-id). This is exceptionally important for typeof usage in macros: often times, rather than inventing an expression with which to provide the right type to a macro, it can be expedient to just pass a type in directly where possible to control the eventual cast or type inside of particularly complex macros. This is done a handful of times in C world. This is not as important for C++, but (some) macros tend to live in a shared world for C and C++.

Some thought was given whether for this proposal to allow decltype( type-id ) in the grammar as a type-based pass-through. An earlier revisions of [n1978] (N1607, to be precise) states, it was left untouched so to let it potentially be used as a "meta"-type value. That paper even casually references the idea of doing decltype(SomeType).is_reference(). We believe it is highly unlikely this will ever happen given the direction reflection has gone in for its facilities (e.g. preferring a sigil/operator or the keyword reflexpr). Furthermore, the overall direction of C++ with <type_traits> has eschewed the idea that there would ever be a used for decltype(SomeType) as a constexpr object-producing expression. It could be safe to take decltype ( type-id ) for C++ to just act as a placeholder for type-id to harmonize its version of typeof with C’s, even if it might not provide any essential value.

But, we do NOT propose it here and consider it outside the scope of this proposal as there is no existing practice for decltype ( type-id ).

This proposal adds typeof and typeof_unqual to C++ to harmonize the 2 languages and close the loophole left from 20 years ago (for C++) and 30 years ago (from C), now that C has finally standardized its long-implemented keyword extensions.

2.1. What About Typical Language Differences for C and C++ with typeof & friends?

The behavior of typeof is intended to be exactly identical to dectlype, just without references. This includes allowing bitfields and similar C++-isms capable from decltype. This follows existing practice, modulo bugs in GCC around the name of destructors (https://godbolt.org/z/v1W5hGdYz).

3. Specification

The specification is relative to the latest C++ Working Draft, [n4950].

3.1. Language Wording

3.1.1. Add to Table 5: Keywords ([tab:lex.key]) 2 new keywords

5.11 Keywords [lex.key]

alignas
constinit
false
public
true
alignof
const_cast
float
register
try
asm
continue
for
reinterpret_cast
typedef
auto
co_await
friend
requires
typeid
bool
co_return
goto
return
typename
break
co_yield
if
short
typeof
case
decltype
inline
signed
typeof_unqual
catch
default
int
sizeof
char
delete
long
static
char8_t
do
mutable
static_assert
char16_t
double
namespace
static_cast
char32_t
dynamic_cast
new
struct
class
else
noexcept
switch
concept
enum
nullptr
template
const
explicit
operator
this
consteval
export
private
thread_local
constexpr
extern
protected
throw

3.1.2. Modify "Decltype specifiers" [dcl.type.decltype]

9.2.9.5 Decltype specifiers [dcl.type.decltype]

decltype-specifier:

decltype ( expression )

typeof ( expression )
typeof ( type-id )
typeof_unqual ( expression )
typeof_unqual ( type-id )

For an expression E, the type denoted by decltype(E) is defined as follows:

  • if E is an unparenthesized id-expression naming a structured binding ([dcl.struct.bind]), decltype(E) is the referenced type as given in the specification of the structured binding declaration;
  • otherwise, if E is an unparenthesized id-expression naming a non-type template-parameter ([temp.param]), decltype(E) is the type of the template-parameter after performing any necessary type deduction ([dcl.spec.auto], [dcl.type.class.deduct]);
  • otherwise, if E is an unparenthesized id-expression or an unparenthesized class member access ([expr.ref]), decltype(E) is the type of the entity named by E. If there is no such entity, the program is ill-formed;
  • otherwise, if E is an xvalue, decltype(E) is T&&, where T is the type of E;
  • otherwise, if E is an lvalue, decltype(E) is T&, where T is the type of E;
  • otherwise, decltype(E) is the type of E.

The operand of the decltype , typeof, or typeof_unqual specifier is an unevaluated operand.

For an expression E, the type denoted by typeof(E) is the type of E. The type denoted by typeof_unqual(E) is formed by removing top-level cv-qualifiers from the type denoted by typeof(E).

For a type-id T, the type denoted by typeof(T) is formed by removing top-level reference qualifiers, if any, from T. The type denoted by typeof_unqual(T) is formed by removing top-level cv-qualifiers and then reference qualifiers from T.

3.1.3. Modify decltype mention in Template Deduction’s "General" Clause [temp.deduct.general] to include typeof and typeof_unqual

13.10.3.1 General [temp.deduct.general]

The deduction substitution loci are

  • the function type outside of the noexcept-specifier,
  • the explicit-specifier, and
  • the template parameter declarations.

The substitution occurs in all types and expressions that are used in the deduction substitution loci. The expressions include not only constant expressions such as those that appear in array bounds or as nontype template arguments but also general expressions (i.e., non-constant expressions) inside sizeof, decltype, typeof, typeof_unqual, and other contexts that allow non-constant expressions. The substitution proceeds in lexical order and stops when a condition that causes deduction to fail is encountered. If substitution into different declarations of the same function template would cause template instantiations to occur in a different order or not at all, the program is ill-formed; no diagnostic required.

3.1.4. Modify decltype mention in Template’s "Type equivalence" Clause [temp.type] to include typeof and typeof_unqual

13.6 Type equivalence [temp.type]

If an expression e is type-dependent, decltype(e) , typeof_unqual(e), or typeof(e) denotes denote a unique dependent type. Two such decltype-specifiers refer to the same type only if their expressions are equivalent ([temp.over.link]).

3.1.5. Modify decltype mentions in Template’s "Dependent types" Clause [temp.dep.type] to include typeof and typeof_unqual

13.8.3.2 Dependent types [temp.dep.type]

A type is dependent if it is

  • a template parameter,
  • denoted by a dependent (qualified) name,
  • a nested class or enumeration that is a direct member of a class that is the current instantiation,
  • a cv-qualified type where the cv-unqualified type is dependent,
  • a compound type constructed from any dependent type,
  • an array type whose element type is dependent or whose bound (if any) is value-dependent,
  • a function type whose parameters include one or more function parameter packs,
  • a function type whose exception specification is value-dependent,
  • denoted by a simple-template-id in which either the template name is a template parameter or any of the template arguments is a dependent type or an expression that is type-dependent or value-dependent or is a pack expansion, or
  • denoted by decltype(expression) , typeof(expression), or typeof_unqual(expression) , where expression is type-dependent . , or
  • denoted by typeof(type-id) or typeof_unqual(type-id), where type-id is type-dependent.

3.1.6. Modify decltype mentions in Template’s "Dependent types" Clause [temp.dep.type] to include typeof and typeof_unqual

13.10.3.6 Deducing template arguments from a type [temp.deduct.type]

The non-deduced contexts are:

  • The nested-name-specifier of a type that was specified using a qualified-id.
  • The expression or type-id of a decltype-specifier.

References

Informative References

[N1978]
Jakko Järvi; Bjarne Stroustrup; Gabriel Dos Reis. N1978 - Decltype (revision 5). April 24th, 2006. URL: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n1978.pdf
[N2343]
Jakko Järvi; Bjarne Stroustrup; Gabriel Dos Reis. N2343 - Decltype (revision 7). July 18th, 2007. URL: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2343.pdf
[N2927]
JeanHeyd Meneide; Shepherd's Oasis, LLC. N2927 - Not-so-magic - typeof for C. July 18th, 2007. URL: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2927.htm
[N4950]
Thomas Köppe. Working Draft, Standard for Programming Language C++. 10 May 2023. URL: https://wg21.link/n4950