Date:  2023-03-24
Project:  Programming Language C++
Reference:  ISO/IEC IS 14882:2020
Reply to:  Jens Maurer
 [email protected]


C++ Standard Core Language Active Issues, Revision 111


This document contains the C++ core language issues on which the Committee (INCITS PL22.16 + WG21) has not yet acted, that is, issues with status "Ready," "Tentatively Ready," "Review," "Drafting," and "Open." (See Issue Status below.)

This document is part of a group of related documents that together describe the issues that have been raised regarding the C++ Standard. The other documents in the group are:

Section references in this document reflect the section numbering of document WG21 N4944.

The purpose of these documents is to record the disposition of issues that have come before the Core Language Working Group of the ANSI (INCITS PL22.16) and ISO (WG21) C++ Standard Committee.

Some issues represent potential defects in the ISO/IEC IS 14882:2020 document and corrected defects in the earlier 2017, 2014, 2011, 2003, and 1998 documents; others refer to text in the working draft for the next revision of the C++ language and not to any Standard text. Issues are not necessarily formal ISO Defect Reports (DRs). While some issues will eventually be elevated to DR status, others will be disposed of in other ways.

The most current public version of this document can be found at http://www.open-std.org/jtc1/sc22/wg21. Requests for further information about these documents should include the document number, reference ISO/IEC 14882:2020, and be submitted to the InterNational Committee for Information Technology Standards (INCITS), 1250 Eye Street NW, Suite 200, Washington, DC 20005, USA.

Information regarding C++ standardization can be found at http://isocpp.org/std.


Revision History

Issue status

Issues progress through various statuses as the Core Language Working Group and, ultimately, the full PL22.16 and WG21 committees deliberate and act. For ease of reference, issues are grouped in these documents by their status. Issues have one of the following statuses:

Open: The issue is new or the working group has not yet formed an opinion on the issue. If a Suggested Resolution is given, it reflects the opinion of the issue's submitter, not necessarily that of the working group or the Committee as a whole.

Drafting: Informal consensus has been reached in the working group and is described in rough terms in a Tentative Resolution, although precise wording for the change is not yet available.

Review: Exact wording of a Proposed Resolution is now available for an issue on which the working group previously reached informal consensus.

Ready: The working group has reached consensus that a change in the working draft is required, the Proposed Resolution is correct, and the issue is ready to forward to the full Committee for ratification.

Tentatively Ready: Like "ready" except that the resolution was produced and approved by a subset of the working group membership between meetings. Persons not participating in these between-meeting activities are encouraged to review such resolutions carefully and to alert the working group with any problems that may be found.

DR: The full Committee has approved the item as a proposed defect report. The Proposed Resolution in an issue with this status reflects the best judgment of the Committee at this time regarding the action that will be taken to remedy the defect; however, the current wording of the Standard remains in effect until such time as a Technical Corrigendum or a revision of the Standard is issued by ISO.

accepted: Like a DR except that the issue concerns the wording of the current Working Paper rather than that of the current International Standard.

TC1: A DR issue included in Technical Corrigendum 1. TC1 is a revision of the Standard issued in 2003.

CD1: A DR issue not resolved in TC1 but included in Committee Draft 1. CD1 was advanced for balloting at the September, 2008 WG21 meeting.

CD2: A DR issue not resolved in CD1 but included in the Final Committee Draft advanced for balloting at the March, 2010 WG21 meeting.

C++11: A DR issue not resolved in CD2 but included in ISO/IEC 14882:2011.

CD3: A DR/DRWP or Accepted/WP issue not resolved in C++11 but included in the Committee Draft advanceed for balloting at the April, 2013 WG21 meeting.

C++14: A DR/DRWP or Accepted/WP issue not resolved in CD3 but included in ISO/IEC 14882:2014.

CD4: A DR/DRWP or Accepted/WP issue not resolved in C++14 but included in the Committee Draft advanced for balloting at the June, 2016 WG21 meeting.

C++17: a DR/DRWP or Accepted/WP issue not resolved in CD4 but included in ISO/IEC 14882:2017.

CD5: A DR/DRWP or Accepted/WP issue not resolved in C++17 but included in the Committee Draft advanced for balloting at the July, 2019 WG21 meeting.

C++20: a DR/DRWP or Accepted/WP issue not resolved in CD5 but included in ISO/IEC 14882:2020.

CD6: A DR/DRWP or Accepted/WP issue not resolved in C++20 but included in the Committee Draft advanced for balloting at the July, 2022 WG21 meeting.

DRWP: A DR issue whose resolution is reflected in the current Working Paper. The Working Paper is a draft for a future version of the Standard.

WP: An accepted issue whose resolution is reflected in the current Working Paper.

Dup: The issue is identical to or a subset of another issue, identified in a Rationale statement.

NAD: The working group has reached consensus that the issue is not a defect in the Standard. A Rationale statement describes the working group's reasoning.

Extension: The working group has reached consensus that the issue is not a defect in the Standard but is a request for an extension to the language. The working group expresses no opinion on the merits of an issue with this status; however, the issue will be maintained on the list for possible future consideration as an extension proposal.

Concepts: The issue relates to the “Concepts” proposal that was removed from the working paper at the Frankfurt (July, 2009) meeting and hence is no longer under consideration.


Overview

Section Issue Status Liaison Title
intro.defs 783 open Definition of “argument”
intro.defs 2632 open 'user-declared' is not defined
4.1  intro.compliance 949 open Requirements for freestanding implementations
4.1.1  intro.compliance.general 2700 review WG14 #error disallows existing implementation practice
5.2  lex.phases 1698 open Files ending in \
5.2  lex.phases 2573 open SG12 Undefined behavior when splicing results in a universal-character-name
5.4  lex.pptoken 369 drafting Are new/delete identifiers or preprocessing-op-or-punc?
5.4  lex.pptoken 1655 drafting Line endings in raw string literals
5.4  lex.pptoken 2574 open SG12 Undefined behavior when lexing unmatched quotes
5.6  lex.token 1901 drafting punctuator referenced but not defined
5.12  lex.operators 189 drafting Definition of operator and punctuator
5.13  lex.literal 1924 review editor Definition of “literal” and kinds of literals
5.13.2  lex.icon 2698 review WG14, EWG Using extended integer types with z suffix
5.13.8  lex.ext 1266 open user-defined-integer-literal overflow
5.13.8  lex.ext 1723 drafting Multicharacter user-defined character literals
5.13.8  lex.ext 1735 drafting Out-of-range literals in user-defined-literals
6.1  basic.pre 1529 drafting Nomenclature for variable vs reference non-static data member
6.3  basic.def.odr 1209 open Is a potentially-evaluated expression in a template definition a “use?”
6.3  basic.def.odr 1897 drafting ODR vs alternative tokens
6.4.1  basic.scope.scope 2488 open Overloading virtual functions and functions with trailing requires-clauses
6.5.1  basic.lookup.general 2480 drafting Lookup for enumerators in modules
6.5.2  class.member.lookup 380 open Definition of "ambiguous base class" missing
6.5.2  class.member.lookup 2567 open Operator lookup ambiguity
6.5.5.1  basic.lookup.qual.general 1089 drafting Template parameters in member selections
6.6  basic.link 2670 open Programs and translation units
6.6  basic.link 2706 open Repeated structured binding declarations
6.7.1  intro.memory 1953 open Data races and common initial sequence
6.7.2  intro.object 2324 drafting Size of base class subobject
6.7.2  intro.object 2325 drafting std::launder and reuse of character buffers
6.7.2  intro.object 2334 open Creation of objects by typeid
6.7.2  intro.object 2469 drafting Implicit object creation vs constant expressions
6.7.3  basic.life 419 open Can cast to virtual base class be done on partially-constructed object?
6.7.3  basic.life 1027 drafting Type consistency and reallocation of scalar types
6.7.3  basic.life 1530 drafting Member access in out-of-lifetime objects
6.7.3  basic.life 1853 drafting Defining “allocated storage”
6.7.3  basic.life 2258 open Storage deallocation during period of destruction
6.7.3  basic.life 2514 review SG12 Modifying const subobjects
6.7.3  basic.life 2551 open "Refers to allocated storage" has no meaning
6.7.3  basic.life 2676 open Replacing a complete object having base subobjects
6.7.3  basic.life 2677 review Replacing union subobjects
6.7.4  basic.indet 1997 drafting Placement new and previous initialization
6.7.5  basic.stc 365 open Storage duration and temporaries
6.7.5  basic.stc 1634 drafting Temporary storage duration
6.7.5  basic.stc 2533 drafting Storage duration of implicitly created objects
6.7.5.5.2  basic.stc.dynamic.allocation 1676 drafting auto return type for allocation and deallocation functions
6.7.5.5.2  basic.stc.dynamic.allocation 1682 open Overly-restrictive rules on function templates as allocation functions
6.7.5.5.2  basic.stc.dynamic.allocation 2073 drafting Allocating memory for exception objects
6.7.5.5.3  basic.stc.dynamic.deallocation 523 open Can a one-past-the-end pointer be invalidated by deleting an adjacent object?
6.7.5.5.3  basic.stc.dynamic.deallocation 2042 drafting Exceptions and deallocation functions
6.7.6  basic.align 1211 drafting Misaligned lvalues
6.7.7  class.temporary 2434 open Mandatory copy elision vs non-class objects
6.7.7  class.temporary 2666 open Lifetime extension through static_cast
6.8  basic.types 350 open WG14 signed char underlying representation for objects
6.8  basic.types 1701 drafting Array vs sequence in object representation
6.8.1  basic.types.general 2519 tentatively ready Object representation of a bit-field
6.8.2  basic.fundamental 146 open Floating-point zero
6.8.2  basic.fundamental 251 open How many signed integer types are there?
6.8.2  basic.fundamental 2689 open Are cv-qualified std::nullptr_t fundamental types?
6.8.4  basic.compound 2544 open Address of past-the-end of a potentially-overlapping subobject
6.9.1  intro.execution 698 open The definition of “sequenced before” is too narrow
6.9.2  intro.multithread 1842 open SG1 Unevaluated operands and “carries a dependency”
6.9.2.2  intro.races 2297 open Unclear specification of atomic operations
6.9.2.2  intro.races 2298 open SG1 Actions and expression evaluation
6.9.2.2  intro.races 2587 open Visible side effects and initial value of an object
6.9.3.2  basic.start.static 371 open Interleaving of constructor calls
6.9.3.2  basic.start.static 1294 open Side effects in dynamic/static initialization
6.9.3.2  basic.start.static 1659 open Initialization order of thread_local template static data members
6.9.3.2  basic.start.static 1986 drafting odr-use and delayed initialization
6.9.3.2  basic.start.static 2148 drafting Thread storage duration and order of initialization
6.9.3.3  basic.start.dynamic 640 open Accessing destroyed local objects of static storage duration
6.9.3.3  basic.start.dynamic 2444 drafting Constant expressions in initialization odr-use
6.9.3.3  basic.start.dynamic 2684 open thread_local dynamic initialization
7.3.6  conv.qual 2438 open Problems in the specification of qualification conversions
7.3.7  conv.prom 2485 tentatively ready Bit-fields in integral promotions
7.3.13  conv.mem 170 tentatively ready Pointer-to-member conversions
7.5.4  expr.prim.id 2503 drafting Unclear relationship among name, qualified name, and unqualified name
7.5.4.3  expr.prim.id.qual 2549 open Implicitly moving the operand of a throw-expression in unevaluated contexts
7.5.4.4  expr.prim.id.dtor 2473 drafting Parentheses in pseudo-destructor calls
7.5.5.2  expr.prim.lambda.closure 1973 tentatively ready Which parameter-declaration-clause in a lambda-expression?
7.5.5.2  expr.prim.lambda.closure 2542 drafting Is a closure type a structural type?
7.5.5.2  expr.prim.lambda.closure 2561 open Conversion to function pointer for lambda with explicit object parameter
7.5.5.3  expr.prim.lambda.capture 2086 drafting Reference odr-use vs implicit capture
7.5.7.1  expr.prim.req.general 2560 open Parameter type determination in a requirement-parameter-list
7.5.7.1  expr.prim.req.general 2565 open Invalid types in the parameter-declaration-clause of a requires-expression
7.6  expr.compound 1642 open Missing requirements for prvalue operands
7.6.1.3  expr.call 2284 open Sequencing of braced-init-list arguments
7.6.1.3  expr.call 2515 open Result of a function call
7.6.1.3  expr.call 2660 open Confusing term "this parameter"
7.6.1.3  expr.call 2688 open Calling explicit object member functions
7.6.1.4  expr.type.conv 914 open EWG Value-initialization of array types
7.6.1.4  expr.type.conv 1521 drafting T{expr} with reference types
7.6.1.4  expr.type.conv 2283 drafting Missing complete type requirements
7.6.1.5  expr.ref 2557 drafting Class member access referring to an unrelated class
7.6.1.5  expr.ref 2705 open Accessing ambiguous subobjects
7.6.1.6  expr.post.incr 742 open Postfix increment/decrement with long bit-field operands
7.6.1.7  expr.dynamic.cast 1965 drafting Explicit casts to reference types
7.6.1.8  expr.typeid 282 open Namespace for extended_type_info
7.6.1.8  expr.typeid 528 open Why are incomplete class types not allowed with typeid?
7.6.1.8  expr.typeid 1954 open typeid null dereference check in subexpressions
7.6.1.9  expr.static.cast 2048 open C-style casts that cast away constness vs static_cast
7.6.1.9  expr.static.cast 2243 drafting Incorrect use of implicit conversion sequence
7.6.2.2  expr.unary.op 232 drafting Is indirection through a null pointer undefined behavior?
7.6.2.4  expr.await 2668 open co_await in a lambda-expression
7.6.2.5  expr.sizeof 2609 open Padding in class types
7.6.2.8  expr.new 267 open Alignment requirement for new-expressions
7.6.2.8  expr.new 473 open Block-scope declarations of allocator functions
7.6.2.8  expr.new 901 drafting Deleted operator delete
7.6.2.8  expr.new 1628 open Deallocation function templates
7.6.2.8  expr.new 2102 drafting Constructor checking in new-expression
7.6.2.8  expr.new 2281 drafting Consistency of aligned operator delete replacement
7.6.2.8  expr.new 2532 open Kind of pointer value returned by new T[0]
7.6.2.8  expr.new 2566 open Matching deallocation for uncaught exception
7.6.2.8  expr.new 2592 open Missing definition for placement allocation/deallocation function
7.6.2.8  expr.new 2623 drafting Invoking destroying operator delete for constructor failure
7.6.2.9  expr.delete 196 open Arguments to deallocation functions
7.6.4  expr.mptr.oper 2593 open Insufficient base class restriction for pointer-to-member expression
7.6.6  expr.add 2013 drafting Pointer subtraction in large array
7.6.6  expr.add 2182 drafting Pointer arithmetic in array-like containers
7.6.6  expr.add 2548 open Array prvalues and additive operators
7.6.9  expr.rel 2696 open SG22 Relational comparisons of pointers to void
7.6.16  expr.cond 2023 drafting Composite reference result type of conditional operator
7.6.16  expr.cond 2316 drafting Simplifying class conversions in conditional expressions
7.6.18  expr.throw 2699 ready Inconsistency of throw-expression specification
7.6.19  expr.ass 1542 drafting Compound assignment of braced-init-list
7.7  expr.const 1255 open Definition problems with constexpr functions
7.7  expr.const 1256 open Unevaluated operands are not necessarily constant expressions
7.7  expr.const 1626 open constexpr member functions in brace-or-equal-initializers
7.7  expr.const 2166 drafting Unclear meaning of “undefined constexpr function”
7.7  expr.const 2186 drafting Unclear point that “preceding initialization” must precede
7.7  expr.const 2192 open Constant expressions and order-of-eval undefined behavior
7.7  expr.const 2301 open Value-initialization and constexpr constructor evaluation
7.7  expr.const 2456 open Viable user-defined conversions in converted constant expressions
7.7  expr.const 2536 open EWG Partially initialized variables during constant initialization
7.7  expr.const 2545 open Transparently replacing objects in constant expressions
7.7  expr.const 2552 open Constant evaluation of non-defining variable declarations
7.7  expr.const 2559 open Defaulted consteval functions
7.7  expr.const 2633 open typeid of constexpr-unknown dynamic type
7.7  expr.const 2656 open Converting consteval lambda to function pointer in non-immediate context
7.7  expr.const 2702 open Constant destruction of reference members
7.7  expr.const 2710 open Loops in constant expressions
8.6.5  stmt.ranged 1680 drafting Including <initializer_list> for range-based for
8.7  stmt.jump 2115 drafting Order of implicit destruction vs release of automatic storage
8.7.4  stmt.return 2495 open Glvalue result of a function call
8.7.5  stmt.return.coroutine 2556 open Unusable promise::return_void
8.8  stmt.dcl 2123 open Omitted constant initialization of local static variables
8.9  stmt.ambig 1223 drafting Syntactic disambiguation and trailing-return-types
9.1  dcl.pre 157 open Omitted typedef declarator
9.2.2  dcl.stc 498 open Storage class specifiers in definitions of class members
9.2.2  dcl.stc 2232 open thread_local anonymous unions
9.2.6  dcl.constexpr 2117 drafting Explicit specializations and constexpr function templates
9.2.6  dcl.constexpr 2531 review Static data members redeclared as constexpr
9.2.9.2  dcl.type.cv 2195 open Unsolicited reading of trailing volatile members
9.2.9.4  dcl.type.elab 144 open Position of friend specifier
9.2.9.4  dcl.type.elab 2634 open Avoid circularity in specification of scope for friend class declarations
9.2.9.6  dcl.spec.auto 1348 drafting Use of auto in a trailing-return-type
9.2.9.6  dcl.spec.auto 1670 drafting auto as conversion-type-id
9.2.9.6  dcl.spec.auto 1868 drafting Meaning of “placeholder type”
9.2.9.6  dcl.spec.auto 2412 review SFINAE vs undeduced placeholder type
9.2.9.6.1  dcl.spec.auto.general 2476 tentatively ready placeholder-type-specifiers and function declarators
9.3.2  dcl.name 1488 drafting abstract-pack-declarators in type-ids
9.3.3  dcl.ambig.res 2228 open EWG Ambiguity resolution for cast to function type
9.3.4.1  dcl.meaning.general 2671 open friend named by a template-id
9.3.4.3  dcl.ref 453 drafting References may only bind to “valid” objects
9.3.4.3  dcl.ref 504 open Should use of a variable in its own initializer require a diagnostic?
9.3.4.3  dcl.ref 2550 open Type "reference to cv void" outside of a declarator
9.3.4.6  dcl.fct 1001 drafting Parameter type adjustment in dependent parameter types
9.3.4.6  dcl.fct 1668 drafting Parameter type determination still not clear enough
9.3.4.6  dcl.fct 1790 open WG14 Ellipsis following function parameter pack
9.3.4.6  dcl.fct 2537 drafting Overbroad grammar for parameter-declaration
9.3.4.6  dcl.fct 2553 open Restrictions on explicit object member functions
9.3.4.7  dcl.fct.default 325 drafting When are default arguments parsed?
9.3.4.7  dcl.fct.default 361 open Forward reference to default argument
9.3.4.7  dcl.fct.default 1580 drafting Default arguments in explicit instantiations
9.3.4.7  dcl.fct.default 1609 open Default arguments and function parameter packs
9.3.4.7  dcl.fct.default 2683 ready Default arguments for member functions of templated nested classes
9.3.4.7  dcl.fct.default 2701 open Default arguments in multiple scopes / inheritance of array bounds in the same scope
9.4  dcl.init 2327 drafting Copy elision for direct-initialization with a conversion function
9.4.1  dcl.init.general 2708 open Parenthesized initialization of arrays
9.4.1  dcl.init.general 2709 open Parenthesized initialization of reference-to-aggregate
9.4.2  dcl.init.aggr 2128 drafting Imprecise rule for reference member initializer
9.4.2  dcl.init.aggr 2149 drafting Brace elision and array length deduction
9.4.3  dcl.init.string 1304 drafting Omitted array bound with string initialization
9.4.4  dcl.init.ref 233 open References vs pointers in UDC overload resolution
9.4.4  dcl.init.ref 1414 drafting Binding an rvalue reference to a reference-unrelated lvalue
9.4.4  dcl.init.ref 1827 drafting Reference binding with ambiguous conversions
9.4.4  dcl.init.ref 2018 drafting Qualification conversion vs reference binding
9.4.4  dcl.init.ref 2657 open Cv-qualification adjustment when binding reference to temporary
9.4.4  dcl.init.ref 2704 open Clarify meaning of "bind directly"
9.4.5  dcl.init.list 1996 drafting Reference list-initialization ignores conversion functions
9.4.5  dcl.init.list 2168 open Narrowing conversions and +/- infinity
9.4.5  dcl.init.list 2252 review editor Enumeration list-initialization from the same type
9.4.5  dcl.init.list 2638 open Improve the example for initializing by initializer list
9.5.1  dcl.fct.def.general 1962 open EWG Type of __func__
9.5.1  dcl.fct.def.general 2144 drafting Function/variable declaration ambiguity
9.5.1  dcl.fct.def.general 2362 open EWG __func__ should be constexpr
9.5.2  dcl.fct.def.default 1854 drafting Disallowing use of implicitly-deleted functions
9.5.2  dcl.fct.def.default 2547 open Defaulted comparison operator function for non-classes
9.5.2  dcl.fct.def.default 2570 open Clarify constexpr for defaulted functions
9.5.4  dcl.fct.def.coroutine 2562 open Exceptions thrown during coroutine startup
9.5.4  dcl.fct.def.coroutine 2563 open EWG Initialization of coroutine result object
9.6  dcl.struct.bind 2340 open Reference collapsing and structured bindings
9.7.1  dcl.enum 1485 drafting Out-of-class definition of member unscoped opaque enumeration
9.7.1  dcl.enum 2131 drafting Ambiguity with opaque-enum-declaration
9.8.2.2  namespace.unnamed 2505 drafting Nested unnamed namespace of inline unnamed namespace
9.9  namespace.udecl 813 open typename in a using-declaration with a non-dependent name
9.9  namespace.udecl 2555 open Ineffective redeclaration prevention for using-declarators
9.9  namespace.udecl 2663 tentatively ready Example for member redeclarations with using-declarations
9.11  dcl.link 1817 drafting Linkage specifications and nested scopes
9.12.1  dcl.attr.grammar 1706 drafting alignas pack expansion syntax
9.12.2  dcl.align 1617 open alignas and non-defining declarations
9.12.2  dcl.align 2223 drafting Multiple alignas specifiers
10.1  module.unit 2541 open Linkage specifications, module purview, and module attachment
10.2  module.interface 2607 drafting Visibility of enumerator names
11.1  class.pre 2637 open Injected-class-name as a simple-template-id
11.2  class.prop 511 open POD-structs with template assignment operators
11.2  class.prop 2463 open EWG Trivial copyability and unions with non-trivial members
11.4  class.mem 1890 drafting Member type depending on definition of member function
11.4.1  class.mem.general 2188 open empty-declaration grammar ambiguity
11.4.1  class.mem.general 2661 open Missing disambiguation rule for pure-specifier vs. brace-or-equal-initializer
11.4.4  special 2595 open "More constrained" for eligible special member functions
11.4.5  class.ctor 1353 tentatively ready Array and variant members and deleted special member functions
11.4.5  class.ctor 1623 drafting Deleted default union constructor and member initializers
11.4.5  class.ctor 1808 drafting Constructor templates vs default constructors
11.4.5.3  class.copy.ctor 1092 drafting Cycles in overload resolution during instantiation
11.4.5.3  class.copy.ctor 1548 drafting Copy/move construction and conversion functions
11.4.5.3  class.copy.ctor 1594 drafting Lazy declaration of special members vs overload errors
11.4.5.3  class.copy.ctor 2203 drafting Defaulted copy/move constructors and UDCs
11.4.5.3  class.copy.ctor 2264 drafting Memberwise copying with indeterminate value
11.4.6  class.copy.assign 1499 drafting Missing case for deleted move assignment operator
11.4.6  class.copy.assign 2329 drafting Virtual base classes and generated assignment operators
11.4.7  class.dtor 1977 drafting Contradictory results of failed destructor lookup
11.4.7  class.dtor 2158 drafting Polymorphic behavior during destruction
11.4.8.3  class.conv.fct 2513 open Ambiguity with requires-clause and operator-function-id
11.4.9.3  class.static.data 1283 drafting Static data members of classes with typedef name for linkage purposes
11.4.9.3  class.static.data 1721 drafting Diagnosing ODR violations for static data members
11.4.9.3  class.static.data 2335 drafting Deduced return types vs member types
11.5  class.union 57 open Empty unions
11.5  class.union 1404 drafting Object reallocation in unions
11.5  class.union 1702 drafting Rephrasing the definition of “anonymous union”
11.5.1  class.union.general 2591 open Implicit change of active union member for anonymous union in union
11.5.1  class.union.general 2675 open start_lifetime_as, placement-new, and active union members
11.7.3  class.virtual 2554 open Overriding virtual functions, also with explicit object parameters
11.8.3  class.access.base 2246 drafting Access of indirect virtual base class constructors
11.8.4  class.friend 718 open EWG Non-class, non-function friend declarations
11.8.4  class.friend 2588 drafting EWG friend declarations and module linkage
11.8.5  class.protected 472 drafting Casting across protected inheritance
11.8.5  class.protected 1883 drafting Protected access to constructors in mem-initializers
11.8.5  class.protected 2187 drafting Protected members and access via qualified-id
11.8.5  class.protected 2244 open Base class access in aggregate initialization
11.9.3  class.base.init 1915 open EWG Potentially-invoked destructors in non-throwing constructors
11.9.3  class.base.init 2056 drafting Member function calls in partially-initialized class objects
11.9.3  class.base.init 2403 drafting Temporary materialization and base/member initialization
11.9.3  class.base.init 2669 open Lifetime extension for aggregate initialization
11.9.4  class.inhctor.init 2504 review Inheriting constructors from virtual base classes
11.9.5  class.cdtor 1517 drafting Unclear/missing description of behavior during construction/destruction
11.9.6  class.copy.elision 6 open Should the optimization that allows a class object to alias another object also allow the case of a parameter in an inline function to alias its argument?
11.9.6  class.copy.elision 1049 open Copy elision through reference parameters of inline functions
11.10.1  class.compare.default 2568 open Access checking during synthesis of defaulted comparison operator
11.10.3  class.spaceship 2703 review Three-way comparison requiring strong ordering for floating-point types, take 2
11.10.4  class.compare.secondary 2546 open Defaulted secondary comparison operators defined as deleted
12.2.2.2.1  over.match.call.general 2687 review Calling an explicit object member function via an address-of-overload-set
12.2.2.2.2  over.call.func 1278 drafting Incorrect treatment of contrived object
12.2.2.2.3  over.call.object 2189 open Surrogate call template
12.2.2.2.3  over.call.object 2564 open Conversion to function pointer with an explicit object parameter
12.2.2.3  over.match.oper 545 open User-defined conversions and built-in operator overload resolution
12.2.2.3  over.match.oper 1919 open Overload resolution for ! with explicit conversion operator
12.2.2.3  over.match.oper 2089 drafting Restricting selection of builtin overloaded operators
12.2.2.7  over.match.ref 2028 drafting Converting constructors in rvalue reference initialization
12.2.2.7  over.match.ref 2108 drafting Conversions to non-class prvalues in reference initialization
12.2.2.8  over.match.list 2194 drafting Impossible case in list initialization
12.2.2.8  over.match.list 2311 open Missed case for guaranteed copy elision
12.2.2.9  over.match.class.deduct 2425 open Confusing wording for deduction from a type
12.2.2.9  over.match.class.deduct 2467 drafting CTAD for alias templates and the deducible check
12.2.2.9  over.match.class.deduct 2471 drafting Nested class template argument deduction
12.2.2.9  over.match.class.deduct 2628 open Implicit deduction guides should propagate constraints
12.2.2.9  over.match.class.deduct 2680 open Class template argument deduction for aggregates with designated initializers
12.2.4.2  over.best.ics 2319 drafting Nested brace initialization from same type
12.2.4.2.1  over.best.ics.general 2525 drafting Incorrect definition of implicit conversion sequence
12.2.4.2.1  over.best.ics.general 2679 open Implicit conversion sequence with a null pointer constant
12.2.4.2.5  over.ics.ref 2077 drafting Overload resolution and invalid rvalue-reference initialization
12.2.4.2.6  over.ics.list 1536 drafting Overload resolution with temporary from initializer list
12.2.4.2.6  over.ics.list 2169 open Narrowing conversions and overload resolution
12.2.4.2.6  over.ics.list 2492 review Comparing user-defined conversion sequences in list-initialization
12.2.4.3  over.ics.rank 1459 open Reference-binding tiebreakers in overload resolution
12.2.4.3  over.ics.rank 1789 open Array reference vs array decay in overload resolution
12.2.4.3  over.ics.rank 2110 drafting Overload resolution for base class conversion and reference/non-reference
12.2.4.3  over.ics.rank 2337 open Incorrect implication of logic ladder for conversion sequence tiebreakers
12.3  over.over 1038 review Overload resolution of &x.static_func
12.3  over.over 2572 open Address of overloaded function with no target
12.4  over.oper 1989 drafting Insufficient restrictions on parameters of postfix operators
12.4.3  over.binary 1549 open Overloaded comma operator with void operand
12.5  over.built 260 open User-defined conversions and built-in operator=
12.5  over.built 954 open Overload resolution of conversion operator templates with built-in types
12.6  over.literal 1620 open User-defined literals and extended integer types
13  temp 205 drafting Templates and static data members
13.1  temp.pre 1463 drafting EWG extern "C" alias templates
13.2  temp.param 1444 drafting Type adjustments of non-type template parameters
13.2  temp.param 1635 drafting How similar are template default arguments to function default arguments?
13.2  temp.param 2395 drafting Parameters following a pack expansion
13.2  temp.param 2617 open Default template arguments for template members of non-template classes
13.3  temp.names 579 open What is a “nested” > or >>?
13.3  temp.names 2450 drafting braced-init-list as a template-argument
13.4  temp.arg 440 open Allow implicit pointer-to-member conversion on nontype template argument
13.4  temp.arg 2105 open When do the arguments for a parameter pack end?
13.4.3  temp.arg.nontype 2043 drafting Generalized template arguments and array-to-pointer decay
13.4.3  temp.arg.nontype 2049 drafting List initializer in non-type template default argument
13.4.3  temp.arg.nontype 2401 drafting Array decay vs prohibition of subobject non-type arguments
13.4.3  temp.arg.nontype 2459 drafting Template parameter initialization
13.4.4  temp.arg.template 2057 drafting Template template arguments with default arguments
13.4.4  temp.arg.template 2398 drafting Template template parameter matching and deduction
13.5.2  temp.constr.constr 2686 open Pack expansion into a non-pack parameter of a concept
13.5.2.3  temp.constr.atomic 2589 open Context of access checks during constraint satisfaction checking
13.6  temp.type 2037 drafting Alias templates and template declaration matching
13.7  temp.decls 1730 drafting Can a variable template have an unnamed type?
13.7.2.3  temp.deduct.guide 2697 open EWG Deduction guides using abbreviated function syntax
13.7.2.3  temp.deduct.guide 2707 open Deduction guides cannot have a trailing requires-clause
13.7.5  temp.friend 1918 open friend templates with dependent scopes
13.7.5  temp.friend 1945 open Friend declarations naming members of class templates in non-templates
13.7.5  temp.friend 2118 open Stateful metaprogramming via friend injection
13.7.6  temp.spec.partial 708 open Partial specialization of member templates of class templates
13.7.6  temp.spec.partial 1647 drafting Type agreement of non-type template arguments in partial specializations
13.7.6  temp.spec.partial 2127 drafting Partial specialization and nullptr
13.7.6  temp.spec.partial 2173 open Partial specialization with non-deduced contexts
13.7.6.1  temp.spec.partial.general 2179 drafting Required diagnostic for partial specialization after first use
13.7.6.2  temp.spec.partial.match 549 drafting Non-deducible parameters in partial specializations
13.7.6.4  temp.spec.partial.member 1755 drafting Out-of-class partial specializations of member templates
13.7.7.2  temp.over.link 310 open Can function templates differing only in parameter cv-qualifiers be overloaded?
13.7.7.2  temp.over.link 2584 open Equivalent types in function template declarations
13.7.7.3  temp.func.order 402 open More on partial ordering of function templates
13.7.7.3  temp.func.order 1157 open Partial ordering of function templates is still underspecified
13.7.7.3  temp.func.order 2160 open Issues with partial ordering
13.7.8  temp.alias 1286 drafting Equivalence of alias templates
13.7.8  temp.alias 1430 open Pack expansion into fixed alias template parameter list
13.7.8  temp.alias 1554 drafting Access and alias templates
13.7.8  temp.alias 1979 drafting Alias template specialization in template member definition
13.7.8  temp.alias 1980 drafting Equivalent but not functionally-equivalent redeclarations
13.7.8  temp.alias 2236 drafting When is an alias template specialization dependent?
13.8  temp.res 1257 open Instantiation via non-dependent references in uninstantiated templates
13.8  temp.res 2067 open Generated variadic templates requiring empty pack
13.8.1  temp.res.general 2462 drafting Problems with the omission of the typename keyword
13.8.1  temp.res.general 2468 drafting Omission of the typename keyword in a member template parameter list
13.8.2  temp.local 186 open Name hiding and template template-parameters
13.8.2  temp.local 459 open Hiding of template parameters by base class members
13.8.3.2  temp.dep.type 1390 drafting Dependency of alias template specializations
13.8.3.2  temp.dep.type 1524 drafting Incompletely-defined class template base
13.8.3.2  temp.dep.type 1619 open Definition of current instantiation
13.8.3.2  temp.dep.type 2074 drafting Type-dependence of local class of function template
13.8.3.3  temp.dep.expr 2275 drafting Type-dependence of function template
13.8.3.3  temp.dep.expr 2487 drafting Type dependence of function-style cast to incomplete array type
13.8.3.3  temp.dep.expr 2600 review Type dependency of placeholder types
13.8.3.5  temp.dep.temp 2090 drafting Dependency via non-dependent base class
13.8.4  temp.dep.res 2 drafting How can dependent names be used in member declarations that appear outside of the class template definition?
13.8.4.1  temp.point 287 drafting Order dependencies in template instantiation
13.8.4.1  temp.point 1845 drafting Point of instantiation of a variable template specialization
13.8.4.1  temp.point 2245 drafting Point of instantiation of incomplete class template
13.8.4.1  temp.point 2250 open Implicit instantiation, destruction, and TUs
13.8.4.1  temp.point 2497 drafting Points of instantiation for constexpr function templates
13.9  temp.spec 1253 open Generic non-template members
13.9  temp.spec 2435 open Alias template specializations
13.9.2  temp.inst 1378 open When is an instantiation required?
13.9.2  temp.inst 1602 review Linkage of specialization vs linkage of template arguments
13.9.2  temp.inst 1856 open Indirect nested classes of class templates
13.9.2  temp.inst 2202 drafting When does default argument instantiation occur?
13.9.2  temp.inst 2222 drafting Additional contexts where instantiation is not required
13.9.2  temp.inst 2263 drafting Default argument instantiation for friends
13.9.2  temp.inst 2265 drafting Delayed pack expansion and member redeclarations
13.9.2  temp.inst 2596 drafting Instantiation of constrained non-template friends
13.9.2  temp.inst 2655 open Instantiation of default arguments in lambda-expressions
13.9.3  temp.explicit 293 open Syntax of explicit instantiation/specialization too permissive
13.9.3  temp.explicit 1046 open What is a “use” of a class specialization?
13.9.3  temp.explicit 1665 drafting Declaration matching in explicit instantiations
13.9.3  temp.explicit 2421 drafting Explicit instantiation of constrained member functions
13.9.3  temp.explicit 2501 drafting Explicit instantiation and trailing requires-clauses
13.9.4  temp.expl.spec 529 drafting Use of template<> with “explicitly-specialized” class templates
13.9.4  temp.expl.spec 1840 drafting Non-deleted explicit specialization of deleted function template
13.9.4  temp.expl.spec 1993 drafting Use of template<> defining member of explicit specialization
13.9.4  temp.expl.spec 2409 drafting Explicit specializations of constexpr static data members
13.10.2  temp.arg.explicit 264 open Unusable template constructors and conversion functions
13.10.2  temp.arg.explicit 2055 drafting Explicitly-specified non-deduced parameter packs
13.10.3  temp.deduct 697 open Deduction rules apply to more than functions
13.10.3  temp.deduct 1172 drafting “instantiation-dependent” constructs
13.10.3  temp.deduct 1322 drafting Function parameter type decay in templates
13.10.3  temp.deduct 1582 drafting Template default arguments and deduction failure
13.10.3  temp.deduct 1844 open Defining “immediate context”
13.10.3  temp.deduct 2054 review Missing description of class SFINAE
13.10.3  temp.deduct 2296 open EWG Are default argument instantiation failures in the “immediate context”?
13.10.3.1  temp.deduct.general 2498 open Partial specialization failure and the immediate context
13.10.3.1  temp.deduct.general 2672 open Lambda body SFINAE is still required, contrary to intent and note
13.10.3.2  temp.deduct.call 503 open Cv-qualified function types in template argument deduction
13.10.3.2  temp.deduct.call 1513 drafting initializer_list deduction failure
13.10.3.2  temp.deduct.call 1584 drafting Deducing function types from cv-qualified types
13.10.3.2  temp.deduct.call 1939 open Argument conversions to nondeduced parameter types revisited
13.10.3.3  temp.deduct.funcaddr 1486 drafting Base-derived conversion in member pointer deduction
13.10.3.5  temp.deduct.partial 1221 open Partial ordering and reference collapsing
13.10.3.5  temp.deduct.partial 1610 drafting Cv-qualification in deduction of reference to array
13.10.3.6  temp.deduct.type 1763 open Length mismatch in template type deduction
13.10.3.6  temp.deduct.type 2328 drafting Unclear presentation style of template argument deduction rules
14.4  except.handle 2172 drafting Multiple exceptions with one exception object
14.4  except.handle 2219 drafting Dynamically-unreachable handlers
14.5  except.spec 2417 open Explicit instantiation and exception specifications
15  cpp 2002 open WG14 White space within preprocessing directives
15.2  cpp.cond 925 open Type of character literals in preprocessor expressions
15.2  cpp.cond 1436 open Interaction of constant expression changes with preprocessor expressions
15.2  cpp.cond 2190 open Insufficient specification of __has_include
15.2  cpp.cond 2575 open SG12 Undefined behavior when macro-replacing "defined" operator
15.3  cpp.include 2576 open SG12 Undefined behavior with macro-expanded #include directives
15.6  cpp.replace 1718 drafting WG14 Macro invocation spanning end-of-file
15.6  cpp.replace 2003 drafting Zero-argument macros incorrectly specified
15.6.1  cpp.replace.general 2577 open SG12 Undefined behavior for preprocessing directives in macro arguments
15.6.3  cpp.stringize 1625 open WG14 Adding spaces between tokens in stringizing
15.6.3  cpp.stringize 1709 drafting Stringizing raw string literals containing newline
15.6.3  cpp.stringize 2578 open SG12 Undefined behavior when creating an invalid string literal via stringizing
15.6.4  cpp.concat 2522 open WG14 Removing placemarker tokens and retention of whitespace
15.6.4  cpp.concat 2579 open SG12 Undefined behavior when token pasting does not create a preprocessing token
15.6.5  cpp.rescan 268 open WG14 Macro name suppression in rescanned replacement text
15.7  cpp.line 2580 open SG12 Undefined behavior with #line
15.7  cpp.line 2693 open WG14 Escape sequences for the string-literal of #line
15.9  cpp.pragma 1889 drafting Unclear effect of #pragma on conformance
15.11  cpp.predefined 2581 open SG12 Undefined behavior for predefined macros
15.12  cpp.pragma.op 897 open _Pragma and extended string-literals
15.12  cpp.pragma.op 2694 open WG14 string-literals of the _Pragma operator
17.13.3  csetjmp.syn 2361 open Unclear description of longjmp undefined behavior
Annex B  implimits 2181 drafting Normative requirements in an informative Annex
Annex C  diff 1944 open New C incompatibilities
C.5  diff.cpp03 1279 drafting Additional differences between C++ 2003 and C++ 2011
C.6  diff.iso 1248 open Updating Annex C to C99



Issues with "Ready" Status


2699. Inconsistency of throw-expression specification

Section: 7.6.18  [expr.throw]     Status: ready     Submitter: Krystian Stasiowski     Date: 2020-04-06

Subclause 7.6.18 [expr.throw] paragraph 2 and 3 specify:

Evaluating a throw-expression with an operand throws an exception (14.2 [except.throw]); the type of the exception object is determined by removing any top-level cv-qualifier s from the static type of the operand and adjusting the type from “array of T” or function type T to “pointer to T”.

A throw-expression with no operand rethrows the currently handled exception (14.4 [except.handle]). The exception is reactivated with the existing exception object; no new exception object is created. The exception is no longer considered to be caught.

This means that throwing a value of type const char[3] would throw a char* rather than const char*, which is not intended.

Proposed resolution (approved by CWG 2023-03-03):

Change in 7.6.18 [expr.throw] paragraph 2 through 4 as follows:

Evaluating a A throw-expression with an operand throws an exception (14.2 [except.throw]); the. The array-to-pointer (7.3.3 [conv.array]) and function-to-pointer (7.3.4 [conv.func]) standard conversions are performed on the operand. The type of the exception object is determined by removing any top-level cv-qualifiers from the static type of the (possibly converted) operand and adjusting the type from “array of T” or function type T to “pointer to T”.

A throw-expression with no operand rethrows the currently handled exception (14.4 [except.handle]). If no exception is presently being handled, the function std:: terminate is invoked (14.6.2 [except.terminate]). The Otherwise, the exception is reactivated with the existing exception object; no new exception object is created. The exception is no longer considered to be caught.

If no exception is presently being handled, evaluating a throw-expression with no operand calls std:: terminate() (14.6.2 [except.terminate]).




2683. Default arguments for member functions of templated nested classes

Section: 9.3.4.7  [dcl.fct.default]     Status: ready     Submitter: Matthew House     Date: 2023-01-11

Subclause 9.3.4.7 [dcl.fct.default] paragraph 6 specifies:

Except for member functions of class templates, the default arguments in a member function definition that appears outside of the class definition are added to the set of default arguments provided by the member function declaration in the class definition; the program is ill-formed if a default constructor (11.4.5.2 [class.default.ctor]), copy or move constructor (11.4.5.3 [class.copy.ctor]), or copy or move assignment operator (11.4.6 [class.copy.assign]) is so declared. Default arguments for a member function of a class template shall be specified on the initial declaration of the member function within the class template.

That rule appears to allow adding default arguments for member functions of classes that are nested within class templates, for example:

  template<class> struct A { struct B { void c(int); }; };
  template<class T> void A<T>::B::c(int = 0) {}

MSVC accepts; gcc and clang reject.

Proposed resolution (approved by CWG 2023-03-03):

Change in 9.3.4.7 [dcl.fct.default] paragraph 6 as follows:

Except for member functions of class templates templated classes, the default arguments in a member function definition that appears outside of the class definition are added to the set of default arguments provided by the member function declaration in the class definition; the program is ill-formed if a default constructor (11.4.5.2 [class.default.ctor]), copy or move constructor (11.4.5.3 [class.copy.ctor]), or copy or move assignment operator (11.4.6 [class.copy.assign]) is so declared. Default arguments for a member function of a templated class template shall be specified on the initial declaration of the member function within the templated class template.





Issues with "Tentatively Ready" Status


2519. Object representation of a bit-field

Section: 6.8.1  [basic.types.general]     Status: tentatively ready     Submitter: Jiang An     Date: 2022-01-20

6.7.2 [intro.object] clearly implies that bit-fields are objects; paragraphs 8-9 contain phrases like “unless an object is a bit-field...” and “a non-bit-field subobject”. However, the definition of “object representation” in 6.8.1 [basic.types.general] paragraph 4 is,

The object representation of an object of type T is the sequence of N unsigned char objects taken up by the object of type T, where N equals sizeof(T).

and thus fails to address bit-fields, which are not necessarily composed of a sequence of complete bytes.

The C Standard (6.2.6.1 paragraph 4) says,

Values stored in bit-fields consist of m bits, where m is the size specified for the bit-field. The object representation is the set of m bits the bit-field comprises in the addressable storage unit holding it.

Presumably similar wording could be adopted for C++.

Proposed resolution (approved by CWG 2023-01-06) [SUPERSEDED]:

Change in 6.8.1 [basic.types.general] paragraph 4 as follows:

The object representation of an object of a type T is the sequence of N unsigned char objects taken up by the a non-bit-field complete object of type T, where N equals sizeof(T). The value representation of an object of a type T is the set of bits in the object representation of T that participate in representing a value of type T. The object and value representation of a non-bit-field complete object of type T are the bytes and bits, respectively, of the object corresponding to the object and value representation of its type. The object representation of a bit-field object is the sequence of N bits taken up by the object, where N is the width of the bit-field (11.4.10 [class.bit]). The value representation of a bit-field object is the set of bits in the object representation that participate in representing its value. Bits in the object representation of a type or object that are not part of the value representation are padding bits. For trivially copyable types, the value representation is a set of bits in the object representation that determines a value, which is one discrete element of an implementation-defined set of values. [ Footnote: ... ]

CWG 2023-02-06

Additional drafting is needed to constrain the definition to complete object types.

Proposed resolution (approved for C++26 by CWG 2023-02-06):

Change in 6.8.1 [basic.types.general] paragraph 4 as follows:

The object representation of an object of a complete object type T is the sequence of N unsigned char objects taken up by the a non-bit-field complete object of type T, where N equals sizeof(T). The value representation of an object of a type T is the set of bits in the object representation of T that participate in representing a value of type T. The object and value representation of a non-bit-field complete object of type T are the bytes and bits, respectively, of the object corresponding to the object and value representation of its type. The object representation of a bit-field object is the sequence of N bits taken up by the object, where N is the width of the bit-field (11.4.10 [class.bit]). The value representation of a bit-field object is the set of bits in the object representation that participate in representing its value. Bits in the object representation of a type or object that are not part of the value representation are padding bits. For trivially copyable types, the value representation is a set of bits in the object representation that determines a value, which is one discrete element of an implementation-defined set of values. [ Footnote: ... ]



2485. Bit-fields in integral promotions

Section: 7.3.7  [conv.prom]     Status: tentatively ready     Submitter: Richard Smith     Date: 2021-04-01

According to 7.3.7 [conv.prom] paragraph 5,

A prvalue for an integral bit-field (11.4.10 [class.bit]) can be converted to a prvalue of type int if int can represent all the values of the bit-field; otherwise, it can be converted to unsigned int if unsigned int can represent all the values of the bit-field. If the bit-field is larger yet, no integral promotion applies to it. If the bit-field has an enumerated type, it is treated as any other value of that type for promotion purposes.

This description has several problems. First, the “bit-field” semantic property only makes sense for glvalue expressions, so it's unclear why these rules are described as applying to a prvalue. Perhaps this should be rephrased as something like “An expression that was a bit-field glvalue prior to the application of the lvalue-to-rvalue conversion”?

Second, suppose that char32_t is wider than int. Per paragraph 2, a char32_t prvalue promotes to unsigned long (because unsigned long is necessarily at least 32 bits wide). But per paragraph 5, a char32_t : 32 bitfield does not promote. This seems inconsistent.

Finally, it is not clear that the usual integral promotions are not applied to bit-fields. This should be made explicit.

Proposed resolution (approved by CWG 2023-02-07):

  1. Insert a paragraph before 7.3.7 [conv.prom] paragraph 1 as follows:

    For the purposes of 7.3.7 [conv.prom], a converted bit-field is a prvalue that is the result of an lvalue-to-rvalue conversion (7.3.2 [conv.lval]) applied to a bit-field (11.4.10 [class.bit]).
  2. Change in 7.3.7 [conv.prom] paragraph 1 as follows:

    A prvalue of that is not a converted bit-field and has an integer type other than bool, char8_t, char16_t, char32_t, or wchar_t whose integer conversion rank (6.8.6 [conv.rank]) is less than the rank of int can be converted to a prvalue of type int if int can represent all the values of the source type; otherwise, the source prvalue can be converted to a prvalue of type unsigned int.
  3. Change in 7.3.7 [conv.prom] paragraph 4 as follows:

    A prvalue of an unscoped enumeration type whose underlying type is fixed (9.7.1 [dcl.enum]) can be converted to a prvalue of its underlying type. Moreover, if integral promotion can be applied to its underlying type, a prvalue of an unscoped enumeration type whose underlying type is fixed can also be converted to a prvalue of the promoted underlying type. [ Note: A converted bit-field of enumeration type is treated as any other value of that type for promotion purposes. -- end note ]
  4. Change in 7.3.7 [conv.prom] paragraph 5 as follows:

    A prvalue for an integral bit-field (11.4.10 [class.bit]) converted bit-field of integral type can be converted to a prvalue of type int if int can represent all the values of the bit-field; otherwise, it can be converted to unsigned int if unsigned int can represent all the values of the bit-field. If the bit-field is larger yet, no integral promotion applies to it. If the bit-field has enumeration type, it is treated as any other value of that type for promotion purposes.
  5. Move 7.3.7 [conv.prom] paragraph 2 after paragraph 5 and change as follows:

    A prvalue of type char8_t, char16_t, char32_t, or wchar_t (6.8.2 [basic.fundamental]) (including a converted bit-field that was not already promoted to int or unsigned int according to the rules above) can be converted to a prvalue of the first of the following types that can represent all the values of its underlying type: int, unsigned int, long int, unsigned long int, long long int, or unsigned long long int. If none of the types in that list can represent all the values of its underlying type, a prvalue of type char8_t, char16_t, char32_t, or wchar_t can be converted to a prvalue of , or its underlying type.



170. Pointer-to-member conversions

Section: 7.3.13  [conv.mem]     Status: tentatively ready     Submitter: Mike Stump     Date: 16 Sep 1999

The descriptions of explicit (7.6.1.9 [expr.static.cast] paragraph 9) and implicit (7.3.13 [conv.mem] paragraph 2) pointer-to-member conversions differ in two significant ways:

  1. In a static_cast, a conversion in which the class in the target pointer-to-member type is a base of the class in which the member is declared is permitted and required to work correctly, as long as the resulting pointer-to-member is eventually dereferenced with an object whose dynamic type contains the member. That is, the class of the target pointer-to-member type is not required to contain the member referred to by the value being converted. The specification of implicit pointer-to-member conversion is silent on this question.

    (This situation cannot arise in an implicit pointer-to-member conversion where the source value is something like &X::f, since you can only implicitly convert from pointer-to-base-member to pointer-to-derived-member. However, if the source value is the result of an explicit "up-cast," the target type of the conversion might still not contain the member referred to by the source value.)

  2. The target type in a static_cast is allowed to be more cv-qualified than the source type; in an implicit conversion, however, the cv-qualifications of the two types are required to be identical.

The first difference seems like an oversight. It is not clear whether the latter difference is intentional or not.

(See also issue 794.)

CWG 2022-11-09

The second concern is NAD; implicit conversions allow chaining a pointer-to-member conversion with a qualification conversion.

Proposed resolution:

  1. Change in 7.3.13 [conv.mem] paragraph 2 as follows:

    A prvalue of type “pointer to member of B of type cv T”, where B is a class type, can be converted to a prvalue of type “pointer to member of D of type cv T”, where D is a complete class derived (11.7 [class.derived]) from B. If B is an inaccessible (11.8 [class.access]), ambiguous (6.5.2 [class.member.lookup]), or virtual (11.7.2 [class.mi]) base class of D, or a base class of a virtual base class of D, a program that necessitates this conversion is ill-formed. If class D does not contain the original member and is not a base class of the class containing the original member, the behavior is undefined. Otherwise, The the result of the conversion refers to the same member as the pointer to member before the conversion took place, but it refers to the base class member as if it were a member of the derived class. The result refers to the member in D's instance of B. Since the result has type “pointer to member of D of type cv T”, indirection through it with a D object is valid. The result is the same as if indirecting through the pointer to member of B with the B subobject of D. The null member pointer value is converted to the null member pointer value of the destination type. [ Footnote: ... ]
  2. Change in 7.6.1.9 [expr.static.cast] paragraph 13 as follows:

    ... If class B contains the original member, or is a base or derived class of the class containing the original member, the resulting pointer to member points to the original member. ...



1973. Which parameter-declaration-clause in a lambda-expression?

Section: 7.5.5.2  [expr.prim.lambda.closure]     Status: tentatively ready     Submitter: Dinka Ranns     Date: 2014-07-16

According to 7.5.5.2 [expr.prim.lambda.closure] paragraph 3,

The closure type for a lambda-expression has a public inline function call operator (for a non-generic lambda) or function call operator template (for a generic lambda) (12.4.4 [over.call]) whose parameters and return type are described by the lambda-expression's parameter-declaration-clause and trailing-return-type respectively, and whose template-parameter-list consists of the specified template-parameter-list, if any.

This is insufficiently precise because the trailing-return-type might itself contain a parameter-declaration-clause.

Suggested resolution [SUPERSEDED]:

Change in 7.5.5.1 [expr.prim.lambda.general] paragraph 5 as follows:

If a lambda-declarator does not include a start with a parenthesized parameter-declaration-clause, it is as if () were inserted at the start of the lambda-declarator. A lambda-expression's parameter-declaration-clause is the (possibly empty) parameter-declaration-clause of the lambda-expression's lambda-declarator. If the lambda-declarator does not include a trailing-return-type, the lambda return type is auto, which is deduced from return statements as described in 9.2.9.6 [dcl.spec.auto].

Proposed resolution (approved by CWG 2023-02-06):

  1. Change in 7.5.5.2 [expr.prim.lambda.closure] paragraph 3 as follows:

    The closure type for a lambda-expression has a public inline function call operator (for a non-generic lambda) or function call operator template (for a generic lambda) (12.4.4 [over.call]) whose parameters and return type are described by those of the lambda-expression's parameter-declaration-clause and trailing-return-type respectively, and whose template-parameter-list consists of the specified template-parameter-list, if any.
  2. Change in 7.5.5.1 [expr.prim.lambda.general] paragraph 5 as follows:

    If a lambda-declarator does not include a parameter-declaration-clause, it is as if () were inserted at the start of the lambda-declarator. A lambda-expression's parameter-declaration-clause is the parameter-declaration-clause of the lambda-expression's lambda-declarator, if any, or empty otherwise. If the lambda-declarator does not include a trailing-return-type, the lambda return type is auto, which is deduced from return statements as described in 9.2.9.6 [dcl.spec.auto].



2476. placeholder-type-specifiers and function declarators

Section: 9.2.9.6.1  [dcl.spec.auto.general]     Status: tentatively ready     Submitter: Davis Herring     Date: 2021-01-29

According to 9.2.9.6.1 [dcl.spec.auto.general] paragraph 3,

The placeholder type can appear with a function declarator in the decl-specifier-seq, type-specifier-seq, conversion-function-id, or trailing-return-type, in any context where such a declarator is valid. If the function declarator includes a trailing-return-type (9.3.4.6 [dcl.fct]), that trailing-return-type specifies the declared return type of the function. Otherwise, the function declarator shall declare a function.

This wording disallows a declaration like

   int f();
   auto (*fp)()=f;

The requirement to declare a function was introduced by the resolution of issue 1892.

Proposed resolution (April, 2021):

Change 9.2.9.6.1 [dcl.spec.auto.general] paragraph 3 as follows:

The placeholder type can appear with a function declarator in the decl-specifier-seq, type-specifier-seq, conversion-function-id, or trailing-return-type, in any context where such a declarator is valid if the function declarator includes a trailing-return-type T (9.3.4.6 [dcl.fct]) or declares a function. If the function declarator includes a trailing-return-type (9.3.4.6 [dcl.fct]), that trailing-return-type specifies In the former case, T is the declared return type of the function. Otherwise, the function declarator shall declare a function. If the declared return type of the a function contains a placeholder type, the return type of the function is deduced from non-discarded return statements, if any, in the body of the function (8.5.2 [stmt.if]).

Additional notes (May, 2021):

It was observed that the proposed resolution above does not address the example in the issue, since fp neither has a trailing-return-type nor declares a function. Presumably another case in which a function declarator with a placeholder return type should be permitted is in the declaration of a variable in which the type is deduced from its initializer.

It was also noted in passing that the deduction in the example is only partial: the parameter-type-list is specified by the declarator and only the return type is deduced from the initializer. Although this example is supported by current implementations, there is implementation divergence in the support of another case in which only part of the variable's type is deduced:

    auto (&ar)[2] = L"a";  // Array bound declared, element type deduced

This issue is related to issue 1892, which prohibited cases like

    std::vector<auto(*)()> v;

The ultimate outcome of the two issues should be:

    int f();
    auto (*fp1)() = f;       // OK
    auto (*fp2)()->int = f;  // OK
    auto (*fp3)()->auto = f; // OK

    template<typename T> struct C { };
    C<auto(*)()> c1;         // Not OK
    C<auto(*)()->int> c2;    // OK
    C<auto(*)()->auto> c3;   // Not OK

Proposed resolution (January, 2023):

  1. Change in 9.2.9.6.1 [dcl.spec.auto.general] paragraph 1 as follows:

    A placeholder-type-specifier designates a placeholder type that will be replaced later, typically by deduction from an initializer.
  2. Change and split 9.2.9.6.1 [dcl.spec.auto.general] paragraph 3 as follows:

    A placeholder type can appear with a function declarator in the decl-specifier-seq, type-specifier-seq, conversion-function-id, or trailing-return-type, in any context where such for a function declarator is valid that includes a trailing-return-type (9.3.4.6 [dcl.fct]). If the function declarator includes a trailing-return-type (9.3.4.6 [dcl.fct]), that trailing-return-type specifies the declared return type of the function.

    Otherwise, the A placeholder type can appear in the decl-specifier-seq or type-specifier-seq in the declared return type of a function declarator shall declare that declares a function. If the declared return type of the function contains a placeholder type, ; the return type of the function is deduced from non-discarded return statements, if any, in the body of the function (8.5.2 [stmt.if]).

  3. Change in 9.2.9.6.1 [dcl.spec.auto.general] paragraph 4 as follows:

    The type of a variable declared using a placeholder type is deduced from its initializer. This use is allowed in an initializing declaration (9.4 [dcl.init]) of a variable. The placeholder type shall appear as one of the decl-specifiers in the decl-specifier-seq and or as one of the type-specifiers in a trailing-return-type that specifies the type that replaces such a decl-specifier; the decl-specifier-seq shall be followed by one or more declarators, each of which shall be followed by a non-empty initializer. [ Example:
      ...
      auto f() -> int;                // OK, f returns int
      auto (*fp)() -> auto = f;       // OK
      ...
    
    -- end example ]
  4. Change in 9.3.4.6 [dcl.fct] paragraph 1 as follows:

    In a declaration T D where D has the form
    D1 ( parameter-declaration-clause ) cv-qualifier-seqopt
      ref-qualifieropt noexcept-specifieropt attribute-specifier-seqopt trailing-return-typeopt
    
    and the type of the contained declarator-id in the declaration T D1 is "derived-declarator-type-list T",:
    • If the trailing-return-type is present, T shall be the single type-specifier auto, and the declared return type of the function type is the type specified by the trailing-return-type.
    • Otherwise, the declared return type of the function type is T.
    theThe type of the declarator-id in D is "derived-declarator-type-list noexceptopt function of parameter-type-list cv-qualifier-seqopt ref-qualifieropt returning T U", where
    • the parameter-type-list is derived from the parameter-declaration-clause as described below,
    • U is the declared return type, and
    • the optional noexcept is present if and only if the exception specification (14.5 [except.spec]) is non-throwing.
    The optional attribute-specifier-seq appertains to the function type.
  5. Remove 9.3.4.6 [dcl.fct] paragraph 2:

    In a declaration T D where D has the form
    D1 ( parameter-declaration-clause ) cv-qualifier-seqopt
      ref-qualifieropt noexcept-specifieropt attribute-specifier-seqopt trailing-return-type
    
    and the type ... The optional attribute-specifier-seq appertains to the function type.
  6. Change in 11.4.8.3 [class.conv.fct] paragraph 1 as follows:

    A declaration whose declarator-id has an unqualified-id that is a conversion-function-id declares a conversion function; its declarator shall be a function declarator (9.3.4.6 [dcl.fct]) of the form
    ptr-declarator noptr-declarator ( parameter-declaration-clause ) cv-qualifier-seqopt
          ref-qualifier-seqopt noexcept-specifieropt attribute-specifier-seqopt parameters-and-qualifiers
    
    where the ptr-declarator noptr-declarator consists solely of an id-expression, an optional attribute-specifier-seq, and optional surrounding parentheses, and the id-expression has one of the following forms: ...
  7. Change in 11.4.8.3 [class.conv.fct] paragraph 2 as follows:

    A conversion function shall have no non-object parameters and shall be a non-static member function of a class or class template X; it specifies a conversion from X to the type specified by its declared return type is the conversion-type-id, interpreted as a type-id (9.3.2 [dcl.name]) and it specifies a conversion from X to the type specified by the conversion-type-id, interpreted as a type-id (9.3.2 [dcl.name]). A decl-specifier in the decl-specifier-seq of a conversion function (if any) shall not be a defining-type-specifier .
  8. Remove 11.4.8.3 [class.conv.fct] paragraph 3:

    The type of the conversion function is “noexceptopt function taking no parameter cv-qualifier-seq opt ref-qualifier opt returning conversion-type-id”.



2663. Example for member redeclarations with using-declarations

Section: 9.9  [namespace.udecl]     Status: tentatively ready     Submitter: Shafik Yaghmour     Date: 2022-11-28

Issue 36 was resolved by P1787R6, but no example was added.

Proposed resolution:

  1. Add an example to 9.9 [namespace.udecl] paragraph 8 as follows:

    [ Example:
    struct C {
      int i;
    };
    
    struct D1 : C { };
    struct D2 : C { };
    
    struct D3 : D1, D2 {
      using D1::i;   // OK; equivalent to using C::i
      using D1::i;   // error: duplicate
      using D2::i;   // error: duplicate, also names C::i
    };
    
    -- end example ]
  2. Change the example in 9.9 [namespace.udecl] paragraph 10 as follows:

      using B::x;
      using A::x;              // OK, hides struct B::x
      using A::x;              // OK, does not conflict with previous using A::x
      x = 99;                  // assigns to A::x
      struct x x1;             // x1 has class type B::x
    }
    

CWG 2023-01-06

There is implementation divergence in handling this example.

CWG 2023-02-07

P1787R6 clarified that the example added to 9.9 [namespace.udecl] paragraph 10 is accepted, even in the non-function case.




1353. Array and variant members and deleted special member functions

Section: 11.4.5  [class.ctor]     Status: tentatively ready     Submitter: Sean Hunt     Date: 2011-08-16

The specification of when a defaulted special member function is to be defined as deleted sometimes overlooks variant and array members.

Proposed resolution (approved by CWG 2023-02-07):

  1. Change in 11.4.5.2 [class.default.ctor] paragraph 2 as follows:

    A defaulted default constructor for class X is defined as deleted if:
    • X is a union that has a variant member with a non-trivial default constructor and no variant member of X has a default member initializer,
    • X is a non-union class that has a variant member M with a non-trivial default constructor and no variant member of the anonymous union containing M has a default member initializer,
    • any non-static data member with no default member initializer (11.4 [class.mem]) is of reference type,
    • any non-variant non-static data member of const-qualified type (or possibly multi-dimensional array thereof) with no brace-or-equal-initializer is not const-default-constructible (9.4 [dcl.init]),
    • X is a union and all of its variant members are of const-qualified type (or possibly multi-dimensional array thereof),
    • X is a non-union class and all members of any anonymous union member are of const-qualified type (or possibly multi-dimensional array thereof),
    • any potentially constructed subobject, except for a non-static data member with a brace-or-equal-initializer or a variant member of a union where another non-static data member has a brace-or-equal-initializer, has class type M (or possibly multi-dimensional array thereof) and either M has no default constructor or overload resolution (12.2 [over.match]) as applied to find M's corresponding constructor results in an ambiguity or in a function that is deleted or inaccessible from the defaulted default constructor either does not result in a usable candidate (12.2.1 [over.match.general]) or, in the case of a variant member, selects a non-trivial function, or
    • any potentially constructed subobject has a type with class type M (or possibly multi-dimensional array thereof) and M has a destructor that is deleted or inaccessible from the defaulted default constructor.
  2. Change in 11.4.5.3 [class.copy.ctor] paragraph 10 as follows:

    ... A defaulted copy/move constructor for a class X is defined as deleted (9.5.3 [dcl.fct.def.delete]) if X has:
    • a potentially constructed subobject of type M (or possibly multi-dimensional array thereof) that cannot be copied/moved because for which overload resolution (12.2 [over.match]), as applied to find M's corresponding constructor, results in an ambiguity or a function that is deleted or inaccessible from the defaulted constructor either does not result in a usable candidate (12.2.1 [over.match.general]) or, in the case of a variant member, selects a non-trivial function,
    • a variant member whose corresponding constructor as selected by overload resolution is non-trivial,
    • any potentially constructed subobject of a type with class type M (or possibly multi-dimensional array thereof) where M has a destructor that is deleted or inaccessible from the defaulted constructor, or,
    • for the copy constructor, a non-static data member of rvalue reference type.
  3. Change in 11.4.6 [class.copy.assign] paragraph 7 as follows:

    A defaulted copy/move assignment operator for class X is defined as deleted if X has:
    • a variant member with a non-trivial corresponding assignment operator and X is a union-like class, or
    • a non-static data member of const non-class type (or possibly multi-dimensional array thereof), or
    • a non-static data member of reference type, or
    • a direct non-static data member of class type M (or possibly multi-dimensional array thereof) or a direct base class M that cannot be copied/moved because overload resolution (12.2 [over.match]), as applied to find M's corresponding assignment operator, results in an ambiguity or a function that is deleted or inaccessible from the defaulted assignment operator either does not result in a usable candidate (12.2.1 [over.match.general]) or, in the case of a variant member, selects a non-trivial function.
  4. Change in 11.4.7 [class.dtor] paragraph 7 as follows:

    A defaulted destructor for a class X is defined as deleted if:
    • X is a union-like class that has a variant member with a non-trivial destructor,
    • any potentially constructed subobject has class type M (or possibly multi-dimensional array thereof) and M has a deleted destructor that is deleted or a destructor that is inaccessible from the defaulted destructor or, in the case of a variant member, is non-trivial,
    • or, for a virtual destructor, lookup of the non-array deallocation function results in an ambiguity or in a function that is deleted or inaccessible from the defaulted destructor.





Issues with "Review" Status


2700. #error disallows existing implementation practice

Section: 4.1.1  [intro.compliance.general]     Status: review     Submitter: Richard Smith     Date: 2023-02-13     Liaison: WG14

The resolution for issue 2518 disallows existing implementation practice, as detailed below:

Suggested resolution:

  1. Change in 4.1.1 [intro.compliance.general] paragraph 2 as follows:

    Furthermore, a conforming implementation
    • shall not accept a preprocessing translation unit containing a #error preprocessing directive (15.8 [cpp.error]), and
    • shall issue at least one diagnostic message for each #warning or #error preprocessing directive not following a #error preprocessing directive in a preprocessing translation unit, and
    • shall not accept a translation unit with a static_assert-declaration that fails (9.1 [dcl.pre]).
  2. Change in 5.1 [lex.separate] paragraph 1 as follows:

    The text of the program is kept in units called source files in this document. A source file together with all the headers (16.4.2.3 [headers]) and source files included (15.3 [cpp.include]) via the preprocessing directive #include, less any source lines skipped by any of the conditional inclusion (15.2 [cpp.cond]) preprocessing directives or by the implementation-defined behavior of any conditionally-supported-directives (15.1 [cpp.pre]), is called a preprocessing translation unit.

CWG 2023-03-03

Permit that #warning can be ignored if another diagnostic is produced.

Proposed resolution:

  1. Change in 4.1.1 [intro.compliance.general] bullet 2.3 as follows:

    • ...
    • Otherwise, if a program contains
      • a violation of any diagnosable rule or,
      • a preprocessing translation unit with a #warning preprocessing directive (15.8 [cpp.error]), or
      • an occurrence of a construct described in this document as “conditionally-supported” when the implementation does not support that construct,
      a conforming implementation shall issue at least one diagnostic message.
  2. Change in 4.1.1 [intro.compliance.general] paragraph 2 as follows:

    Furthermore, a conforming implementation shall not accept
    • a preprocessing translation unit containing a #error preprocessing directive (15.8 [cpp.error]), or
    • shall issue at least one diagnostic message for each #warning or #error preprocessing directive not following a #error preprocessing directive in a preprocessing translation unit, and
    • shall not accept a translation unit with a static_assert-declaration that fails (9.1 [dcl.pre]).
  3. Change in 5.1 [lex.separate] paragraph 1 as follows:

    The text of the program is kept in units called source files in this document. A source file together with all the headers (16.4.2.3 [headers]) and source files included (15.3 [cpp.include]) via the preprocessing directive #include, less any source lines skipped by any of the conditional inclusion (15.2 [cpp.cond]) preprocessing directives, as modified by the implementation-defined behavior of any conditionally-supported-directives (15.1 [cpp.pre]) and pragmas (15.9 [cpp.pragma]), if any, is called a preprocessing translation unit.
  4. Change in 15.8 [cpp.error] as follows:

    A preprocessing directive of either of the following forms the form
    # error pp-tokensopt new-line
    
    renders the program ill-formed. A preprocessing directive of the form
    # warning pp-tokensopt new-line
    
    causes requires the implementation to produce a at least one diagnostic message for the preprocessing translation unit (4.1.1 [intro.compliance.general]) that.

    Recommended practice: Any diagnostic message caused by either of these directives should include the specified sequence of preprocessing tokens; the #error directive renders the program ill-formed.




1924. Definition of “literal” and kinds of literals

Section: 5.13  [lex.literal]     Status: review     Submitter: Saeed Amrollah Boyouki     Date: 2014-05-12     Liaison: editor

The term “literal” is used without definition except the implicit connection with the syntactic nonterminal literal. The relationships of English terms to syntactic nonterminals (such as “integer literal” and integer-literal) should be examined throughout 5.13 [lex.literal] and its subsections.

Notes from the November, 2016 meeting:

This issue will be handled editorially. It is being placed in "review" status until that point.




2698. Using extended integer types with z suffix

Section: 5.13.2  [lex.icon]     Status: review     Submitter: Mike Miller     Date: 2023-02-17     Liaison: WG14, EWG

Subclause 5.13.2 [lex.icon] paragraph 4 specifies:

If an integer-literal cannot be represented by any type in its list and an extended integer type (6.8.2 [basic.fundamental]) can represent its value, it may have that extended integer type. If all of the types in the list for the integer-literal are signed, the extended integer type shall be signed. If all of the types in the list for the integer-literal are unsigned, the extended integer type shall be unsigned. If the list contains both signed and unsigned types, the extended integer type may be signed or unsigned. A program is ill-formed if one of its translation units contains an integer-literal that cannot be represented by any of the allowed types.

This implies that an integer-literal with a z suffix can be of extended integer type, if the literal is larger than what is representable in std::size_t.

According to the author of the paper P0330R8 (Literal Suffix for (signed) size_t) introducing the feature, this is unintentional; z should only yield std::size_t and its corresponding signed integer type.

See also the corresponding WG14 paper N2998 Literal Suffixes for size_t.

Proposed resolution (reviewed by CWG 2023-03-03):

Change in 5.13.2 [lex.icon] paragraph 4 as follows:

If Except for integer-literals containing a size-suffix, if the value of an integer-literal cannot be represented by any type in its list and an extended integer type (6.8.2 [basic.fundamental]) can represent its value, it may have that extended integer type. If all of the types in the list for the integer-literal are signed, the extended integer type shall be is signed. If all of the types in the list for the integer-literal are unsigned, the extended integer type shall be is unsigned. If the list contains both signed and unsigned types, the extended integer type may be signed or unsigned. A program is ill-formed if one of its translation units contains If an integer-literal that cannot be represented by any of the allowed types, the program is ill-formed. [ Note: An integer-literal with a z or Z suffix is ill-formed if it cannot be represented by std::size_t. -- end note ]

Additional notes (February, 2023)

Alerted the chair of SG22 (C/C++ Liaison).

Forwarded to EWG at the request of the EWG chair via cplusplus/papers#1467.




2514. Modifying const subobjects

Section: 6.7.3  [basic.life]     Status: review     Submitter: Jiang An     Date: 2021-11-07     Liaison: SG12

The change in C++20 for RU007 allows transparently replacing a const subobject whose complete object is not const, and the new object can be non-const. However, if the reuse of the object has not happened, modifying such subobjects is still undefined behavior.

This restriction causes problems in the implementation of std::map and std::unordered_map; see this bug report. Here, the key_type objects in map containers are const, and implementations generally can't replace these objects after construction.

Perhaps these restrictions can be relaxed to assist in this case: if

a const subobject could be modified.

(Is it meaningful to allow a new-expression like new const int(42) to create cv-qualified objects? Perhaps such objects should be unqualified, while maintaining the cv-qualification in the type of the expression?)

Notes from the November, 2022 meeting

The advice of SG12 is solicited; see cplusplus/papers#1395.




2677. Replacing union subobjects

Section: 6.7.3  [basic.life]     Status: review     Submitter: Richard Smith     Date: 2022-12-06

The resolution to NB comment US 041 (C++20 CD) does not seem to have fully addressed the original issue, allowing:

  union U { int i, j; };
  U u;
  new (&u) U{.i = 5};
  int k = u.j;  // OK! New U::i transparently replaces existing u.j!

The suggestion is to allow a newly-created complete object to transparently replace any object of the same type in the same storage, except for a potentially-overlapping subobject or a const complete object, and to allow a newly-created subobject to transparently replace only a corresponding subobject of an existing object.

Suggested resolution [SUPERSEDED]:

Change in 6.7.3 [basic.life] paragraph 8 as follows:

... An object o1 is transparently replaceable by an object o2 if:

Additional notes (February, 2023)

The suggested resolution above does not handle the additional example in issue 2676.

Suggested resolution:

  1. Change in 6.7.2 [intro.object] paragraph 2 as follows:

    Objects can contain other objects, called subobjects. A subobject can be a member subobject (11.4 [class.mem]), a base class subobject (11.7 [class.derived]), or an array element. An object that is not a subobject of any other object is called a complete object. If an object is created in storage associated with a member subobject or array element subobject e (which may or may not be within its lifetime), the created object is a subobject of e's containing object if:
    • the lifetime of e's containing object has begun and not ended, and
    • the storage for the new object exactly overlays the storage location associated with e, and
    • e is not a potentially-overlapping subobject, and
    • the new object is of the same type as e (ignoring cv-qualification).
    In this case, e and the created object are corresponding direct subobjects.
  2. Change in 6.7.3 [basic.life] paragraph 8 as follows:

    ... An object o1 is transparently replaceable by an object o2 if either
    • o1 and o2 are complete objects for which:
      • o1 is not const,
      • the storage that o2 occupies exactly overlays the storage that o1 occupied, and
      • o1 and o2 are of the same type (ignoring the top-level cv-qualifiers), and or
      • o1 is not a const, complete object, and
      • neither o1 nor o2 is a potentially-overlapping subobject (6.7.2 [intro.object]), and
    • either o1 and o2 are both complete objects, or o1 and o2 are corresponding direct subobjects of objects p1 and p2, respectively, and p1 is transparently replaceable by p2 (6.7.2 [intro.object]) for which:
      • the complete object of o1 is not const or
      • o1 is a mutable member subobject or a subobject thereof.



2531. Static data members redeclared as constexpr

Section: 9.2.6  [dcl.constexpr]     Status: review     Submitter: Davis Herring     Date: 2022-02-16

C++17 made constexpr static data members implicitly inline (9.2.6 [dcl.constexpr] paragraph 1):

A function or static data member declared with the constexpr or consteval specifier is implicitly an inline function or variable (9.2.8 [dcl.inline]).

However, that makes the following well-formed C++14 program ill-formed, no diagnostic required, per 9.2.8 [dcl.inline] paragraph 5:

If a function or variable with external or module linkage is declared inline in one definition domain, an inline declaration of it shall be reachable from the end of every definition domain in which it is declared; no diagnostic is required.
  // x.hh
  struct X {
    static const int x;
  };

  // TU 1
  #include "x.hh"
  constexpr int X::x{};

  // TU 2
  #include "x.hh"
  int main() { return !&X::x; }

Proposed resolution (approved by CWG 2023-02-07):

Change 9.2.6 [dcl.constexpr] paragraph 1 as follows:

A function or static data member declared with the constexpr or consteval specifier on its first declaration is implicitly an inline function or variable (9.2.8 [dcl.inline]).

Drafting note: Functions must be declared constexpr on every declaration if on any, so this isn't a change for them.




2412. SFINAE vs undeduced placeholder type

Section: 9.2.9.6  [dcl.spec.auto]     Status: review     Submitter: Mike Miller     Date: 2019-05-03

The status of the following example is not clear:

  template <typename T> auto foo(T);  // Not defined

  template <typename T> struct FooCallable {
    template<class U>
    static constexpr bool check_foo_callable(...) { return false; }

    template<class U, class = decltype(foo(U{})) >
    static constexpr bool check_foo_callable(int) { return true; }

    static constexpr bool value = check_foo_callable<T>(0);
  };
  static_assert(FooCallable<int>::value == false, "");

The static_assert causes the evaluation of the default template argument decltype(foo<int>(int{})). However, foo is not defined, leaving it with an undeduced placeholder return type. This situation could conceivably be handled in two different ways. According to 9.2.9.6 [dcl.spec.auto] paragraph 9,

If the name of an entity with an undeduced placeholder type appears in an expression, the program is ill-formed.

This would thus appear to be an invalid expression resulting from substitution in the immediate context of the declaration and thus a substitution failure.

The other alternative would be to treat the presence of an undeduced placeholder type for a function template as satisfying the requirements of 13.9.2 [temp.inst] paragraph 4,

Unless a function template specialization has been explicitly instantiated or explicitly specialized, the function template specialization is implicitly instantiated when the specialization is referenced in a context that requires a function definition to exist or if the existence of the definition affects the semantics of the program.

and attempt to instantiate foo<int>. That instantiation fails because the definition is not provided, which would then be an error outside the immediate context of the declaration and thus a hard error instead of substitution failure.

CWG 2022-11-10

There is no implementation divergence on the handling of this example.

Possible resolution:

Change in 9.2.9.6.1 [dcl.spec.auto.general] paragraph 11 as follows:

If a variable or function with an undeduced placeholder type is named by an expression (6.3 [basic.def.odr]), the program is ill-formed. Once a non-discarded return statement has been seen in a function, however, the return type deduced from that statement can be used in the rest of the function, including in other return statements. [ Example: ...
  template <typename T> auto f(T);     // not defined

  template <typename T> struct F {
    template<class U>
    static constexpr bool g(...) { return false; }

    template<class U, class = decltype(f(U{})) >
    static constexpr bool g(int) { return true; }

    static constexpr bool value = g<T>(0);
  };
  static_assert(F<int>::value == false, "");
-- end example ]



2252. Enumeration list-initialization from the same type

Section: 9.4.5  [dcl.init.list]     Status: review     Submitter: Richard Smith     Date: 2016-03-22     Liaison: editor

According to 9.4.5 [dcl.init.list] bullet 3.8,

Otherwise, if T is an enumeration with a fixed underlying type (9.7.1 [dcl.enum]), the initializer-list has a single element v, and the initialization is direct-list-initialization, the object is initialized with the value T(v) (7.6.1.4 [expr.type.conv]); if a narrowing conversion is required to convert v to the underlying type of T , the program is ill-formed.

This could be read as requiring that there be a conversion from v to the underlying type of T, leaving the status of an example like the following unclear:

  enum class E {};
  struct X { operator E(); };
  E{X()}; // ok? 

Notes from the March, 2018 meeting:

CWG disagreed that the existing wording requires such a conversion, only that if such a conversion is possble, it must not narrow. A formulation along the lines of “if that initialization involves a narrowing conversion to the underlying type of T...” was suggested to clarify the intent. This will be handled editorially, and the issue will be left in "review" status until the change has been verified.




2504. Inheriting constructors from virtual base classes

Section: 11.9.4  [class.inhctor.init]     Status: review     Submitter: Hubert Tong     Date: 2021-11-03

According to 11.9.4 [class.inhctor.init] paragraph 1,

When a constructor for type B is invoked to initialize an object of a different type D (that is, when the constructor was inherited (9.9 [namespace.udecl])), initialization proceeds as if a defaulted default constructor were used to initialize the D object and each base class subobject from which the constructor was inherited, except that the B subobject is initialized by the invocation of the inherited constructor. The complete initialization is considered to be a single function call; in particular, the initialization of the inherited constructor's parameters is sequenced before the initialization of any part of the Dobject.

First, this assumes that the base class constructor will be invoked from the derived class constructor, which will not be true if the base is virtual and initialized by a more-derived constructor.

If the call to the virtual base constructor is omitted, the last sentence is unclear whether the initialization of the base class constructor's parameters by the inheriting constructor occurs or not. There is implementation divergence in the initialization of V's parameter in the following example:

  struct NonTriv {
    NonTriv(int);
    ~NonTriv();
  };
  struct V { V() = default; V(NonTriv); };
  struct Q { Q(); };
  struct A : virtual V, Q {
    using V::V;
    A() : A(42) { }
  };
  struct B : A { };
  void foo() { B b; }

CWG telecon 2022-09-23:

Inheriting constructors from a virtual base class ought to be ill-formed. Inform EWG accordingly.

Possible resolution [SUPERSEDED]:

  1. Change in 9.9 [namespace.udecl] paragraph 3 as follows:

    ... If a using-declarator names a constructor, its nested-name-specifier shall name a direct non-virtual base class of the current class. If the immediate (class) scope is associated with a class template, it shall derive from the specified base class or have at least one dependent base class.
  2. Change the example in 11.9.4 [class.inhctor.init] paragraph 1 as follows:

    D2 f(1.0);  // error: B1 has a deleted no default constructor
    
    struct W { W(int); };
    struct X : virtual W { using W::W; X() = delete; };
    struct Y : X { using X::X; };
    struct Z : Y, virtual W { using Y::Y; };
    Z z(0);  // OK, initialization of Y does not invoke default constructor of X
    
  3. Change the example in 11.9.4 [class.inhctor.init] paragraph 2 as follows:

    struct V1 : virtual B { using B::B; };
    struct V2 : virtual B { using B::B; };
    
    struct D2 : V1, V2 {
      using V1::V1;
      using V2::V2;
    };
    D1 d1(0);  // error: ambiguous
    D2 d2(0);  // OK, initializes virtual B base class, which initializes the A base class
               // then initializes the V1 and V2 base classes as if by a defaulted default constructor
    

CWG telecon 2022-10-07:

Given that there are examples that discuss inheriting constructors from virtual base classes and given the existing normative wording, making it clear that NonTriv is not constructed, CWG felt that the implementation divergence is best addressed by amending the examples.

Possible resolution:

Add another example before 11.9.4 [class.inhctor.init] paragraph 2 as follows:

[ Example:

struct NonTriv {
  NonTriv(int);
  ~NonTriv();
};
struct V { V() = default; V(NonTriv); };
struct Q { Q(); };
struct A : virtual V, Q {
  using V::V;
  A() : A(42) { }    // #1, A(42) is equivalent to V(42)
};
struct B : A { };
void foo() { B b; }

In this example, the V subobject of b is constructed using the defaulted default constructor. The mem-initializer naming the constructor inherited from V at #1 is not evaluated and thus no object of type NonTriv is constructed. -- end example ]

If the constructor was inherited from multiple base class subobjects of type B, the program is ill-formed.

Additional notes (October, 2022)

Possible resolution:

  1. Change in 11.9.4 [class.inhctor.init] paragraph 1 as follows:

    When a constructor for type B is invoked to initialize an object of a different type D (that is, when the constructor was inherited (9.9 [namespace.udecl])), initialization proceeds as if a defaulted default constructor were used to initialize the D object and each base class subobject from which the constructor was inherited, if the base class subobject were to be initialized as part of the D object (11.9.3 [class.base.init]), except that the B subobject is initialized by the invocation of the inherited constructor. The invocation of the inherited constructor, including the evaluation of any arguments, is omitted if the B subobject is not to be initialized as part of the D object. The complete initialization is considered to be a single function call; in particular, unless omitted, the initialization of the inherited constructor's parameters is sequenced before the initialization of any part of the Dobject.
  2. Add another example before 11.9.4 [class.inhctor.init] paragraph 2 as follows:

    [ Example:

    struct V { V() = default; V(int); };
    struct Q { Q(); };
    struct A : virtual V, Q {
      using V::V;
      A() = delete;
    };
    int bar() { return 42; }
    struct B : A {
      B() : A(bar()) {}  // ok
    };
    struct C : B {};
    void foo() { C c; } // bar is not invoked, because the V subobject is not initialized as part of B
    

    -- end example ]

CWG telecon 2022-10-21:

This is an ABI break for implementations when transitioning to the C++17 model for inheriting constructors.




2703. Three-way comparison requiring strong ordering for floating-point types, take 2

Section: 11.10.3  [class.spaceship]     Status: review     Submitter: Richard Smith     Date: 2023-02-13

The resolution accepted for issue 2539 does not actually address the example in the issue, because overload resolution is never performed for expressions involved only built-in types.

Suggested resolution:

Change in 11.10.3 [class.spaceship] paragraph 1 as follows:

The synthesized three-way comparison of type R (17.11.2 [cmp.categories]) of glvalues a and b of the same type is defined as follows:



2687. Calling an explicit object member function via an address-of-overload-set

Section: 12.2.2.2.1  [over.match.call.general]     Status: review     Submitter: Matthew House     Date: 2023-01-16

Subclause 12.2.2.2.1 [over.match.call.general] paragraph 2 specifies:

If the postfix-expression is the address of an overload set, overload resolution is applied using that set as described above. If the function selected by overload resolution is a non-static member function, the program is ill-formed.

However, 7.6.2.2 [expr.unary.op] paragraph 3 states that the address of an explicit object member function is a plain pointer to function, not a pointer to member. The former can be invoked using the regular function call syntax (7.6.1.3 [expr.call]) without the need for a pointer-to-member expression (7.6.4 [expr.mptr.oper]). For example, absent any overloading and given some function f, the expression (&A::f)(A()) could be valid if f is a static member function or an explicit object member function. However, that expression cannot possibly be valid if f is an implicit object member function.

Proposed resolution (approved by CWG 2023-01-27):

Change in 12.2.2.2.1 [over.match.call.general] paragraph 2 as follows:

If the postfix-expression is the address of an overload set, overload resolution is applied using that set as described above. If the function selected by overload resolution is a non-static an implicit object member function, the program is ill-formed.

EWG 2023-02-06

Subclause 12.3 [over.over] paragraph 4 says that explicit-object member functions match both kinds of pointer type, and 11.4.3 [class.mfct.non.static] paragraph 2 seems to try to transform (&A::f)(A()) into (&(*this).A::f)(A()) if f is an explicit-object member function.

Editor's observation: 12.3 [over.over] paragraph 4 was a missed edit applying P0847R7; it was rectified with commit 0c9dd96bb on 2023-01-17.

Additional notes (March, 2023)

The concern about incorrectly applying the this transformation was addressed by P2797R0 (Proposed resolution for CWG2692 Static and explicit object member functions with the same parameter-type-lists).




2492. Comparing user-defined conversion sequences in list-initialization

Section: 12.2.4.2.6  [over.ics.list]     Status: review     Submitter: Jim X     Date: 2021-01-11

Consider the following example:

  #include <initializer_list>
  struct A{
    operator short(){
      return 0;
    }
  };
  struct B{
    operator bool(){
      return 0;
    }
  };
  void fun(std::initializer_list<int>){}
  void fun(std::initializer_list<bool>){}
  int main(){
    fun({A{},B{}});
  }

According to 12.2.4.2.6 [over.ics.list] paragraph 6,

Otherwise, if the parameter type is std::initializer_list<X> and all the elements of the initializer list can be implicitly converted to X, the implicit conversion sequence is the worst conversion necessary to convert an element of the list to X, or if the initializer list has no elements, the identity conversion. This conversion can be a user-defined conversion even in the context of a call to an initializer-list constructor.

In this example, all of the conversions from list elements to the initializer_list template argument type are user-defined conversions. According to 12.2.4.3 [over.ics.rank] bullet 3.3,

User-defined conversion sequence U1 is a better conversion sequence than another user-defined conversion sequence U2 if they contain the same user-defined conversion function or constructor or they initialize the same class in an aggregate initialization and in either case the second standard conversion sequence of U1 is better than the second standard conversion sequence of U2.

Since in both cases the two elements of the initializer-list argument involve different user-defined conversion functions, the two user-defined conversion sequences for the elements cannot be distinguished, so the determination of the “worst conversion” for the two candidates does not consider the second standard conversion sequence. This presumably makes it impossible to distinguish the conversion sequences for the two candidates in the function call, making the call ambiguous.

However, there is implementation divergence on the handling of this example, with g++ reporting an ambiguity and clang, MSVC, and EDG calling the int overload, presumably on the basis that short->int is a promotion while short->bool is a conversion.

Notes from the August, 2021 teleconference:

CWG agreed with the reasoning expressed in the analysis, that conversions involving different user-defined conversion functions cannot be compared, and thus the call is ambiguous. The use of the phrase “worst conversion” is insufficiently clear, however, and requires definition.

Proposed resolution, August, 2021:

Change 12.2.4.2.6 [over.ics.list] paragraphs 5 and 6 as follows:

Otherwise, if the parameter type is std::initializer_list<X> and either the initializer list is empty or all the elements of the initializer list can be implicitly converted to X, the implicit conversion sequence is the worst conversion worst conversion necessary to convert an element of the list to X, or if defined as follows. If the initializer list has no elements, the worst conversion is the identity conversion. Otherwise, the worst conversion is an implicit conversion sequence for a list element that is not better than any other implicit conversion sequence required by list elements, compared as described in 12.2.4.3 [over.ics.rank]. If more than one implicit conversion sequence satisfies this criterion, then if they are user-defined conversion sequences that do not all contain the same user-defined conversion function or constructor, the worst conversion sequence is the ambiguous conversion sequence (12.2.4.2.1 [over.best.ics.general]); otherwise, it is unspecified which of those conversion sequences is chosen as worst. This conversion can be a user-defined conversion even in the context of a call to an initializer-list constructor. [Example 2:

  void f(std::initializer_list<int>);
  f( {} );        // OK: f(initializer_list<int>) identity conversion
  f( {1,2,3} );   // OK: f(initializer_list<int>) identity conversion
  f( {'a','b'} ); // OK: f(initializer_list<int>) integral promotion
  f( {1.0} );     // error: narrowing

  struct A {
    A(std::initializer_list<double>);            // #1
    A(std::initializer_list<complex<double>>);   // #2
    A(std::initializer_list<std::string>);       // #3
  };
  A a{ 1.0,2.0 };        // OK, uses #1

  void g(A);
  g({ "foo", "bar" });   // OK, uses #3

  typedef int IA[3];
  void h(const IA&);
  h({ 1, 2, 3 });        // OK: identity conversion

  void x(std::initializer_list<int>);
  void x(std::initializer_list<bool>);
  struct S1 { operator short(); };
  struct S2 { operator bool(); };
  void y() {
    x({S1{}, S2{}});   // error: ambiguous. The ICSes for each list element are indistinguishable because
                       // they do not contain the same conversion function, so the worst conversion is
                       // the ambiguous conversion sequence.
  }

end example]

Otherwise, if the parameter type is “array of N X ” or “array of unknown bound of X”, if there exists an implicit conversion sequence from each element of the initializer list (and from {} in the former case if N exceeds the number of elements in the initializer list) to X, the implicit conversion sequence is the worst such implicit conversion sequence conversion necessary to convert an element of the list (including, if there are too few list elements, {}) to X, determined as described above for a std::initializer_list<X> with a non-empty initializer list.

Editorial issue required: define "worst conversion sequence"




1038. Overload resolution of &x.static_func

Section: 12.3  [over.over]     Status: review     Submitter: Mike Miller     Date: 2010-03-02

The Standard is not clear whether the following example is well-formed or not:

    struct S {
        static void f(int);
        static void f(double);
    };
    S s;
    void (*pf)(int) = &s.f;

According to 7.6.1.5 [expr.ref] bullet 4.3, you do function overload resolution to determine whether x.f is a static or non-static member function. 7.6.2.2 [expr.unary.op] paragraph 6 says that you can only take the address of an overloaded function in a context that determines the overload to be chosen, and the initialization of a function pointer is such a context (12.3 [over.over] paragraph 1) . The problem is that 12.3 [over.over] is phrased in terms of “an overloaded function name,” and this is a member access expression, not a name.

There is variability among implementations as to whether this example is accepted; some accept it as written, some only if the & is omitted, and some reject it in both forms.

Additional note (October, 2010):

A related question concerns an example like

    struct S {
        static void g(int*) {}
        static void g(long) {}
    } s;

    void foo() {
        (&s.g)(0L);
    }

Because the address occurs in a call context and not in one of the contexts mentioned in 12.3 [over.over] paragraph 1, the call expression in foo is presumably ill-formed. Contrast this with the similar example

    void g1(int*) {}
    void g1(long) {}

    void foo1() {
        (&g1)(0L);
    }

This call presumably is well-formed because 12.2.2.2 [over.match.call] applies to “the address of a set of overloaded functions.” (This was clearer in the wording prior to the resolution of issue 704: “...in this context using &F behaves the same as using the name F by itself.”) It's not clear that there's any reason to treat these two cases differently.

This question also bears on the original question of this issue, since the original wording of 12.2.2.2 [over.match.call] also described the case of an ordinary member function call like s.g(0L) as involving the “name” of the function, even though the postfix-expression is a member access expression and not a “name.” Perhaps the reference to “name” in 12.3 [over.over] should be similarly understood as applying to member access expressions?

Additional notes (February, 2023)

This appears to be resolved, in part by P1787R6 (accepted November, 2020).




2600. Type dependency of placeholder types

Section: 13.8.3.3  [temp.dep.expr]     Status: review     Submitter: Hubert Tong     Date: 2022-06-18

Subclause 13.8.3.2 [temp.dep.type] paragraph 7 has a list of types considered to be dependent. This list covers placeholder types only insofar as it has an entry about decltype(expression). Subclause 13.8.3.3 [temp.dep.expr] paragraph 3 has a list of expression forms not considered dependent unless specific types named by the expressions are dependent. This list includes forms where placeholder types are allowed. For example, the wording does not say that the new-expression at #1 (below) is dependent, but it ought to be:

  template <typename T> struct A { A(bool, T); };

  void g(...);

  template <typename T>
  auto f(T t) { return g(new A(t, 0)); }  // #1

  int g(A<int> *);
  int h() { return f<void *>(nullptr); }

Some implementation even treats an obviously non-dependent case as dependent:

  template <typename T, typename U> struct A { A(T, U); };

  void g(...); // #1

  template <typename T>
  auto f() { return g(new A(0, 0)); } // #1 or #2?

  int g(A<int, int> *); // #2
  void h() { return f<void *>(); }

A similar example that is non-dependent:

  template <typename T, typename U = T> struct A { A(T, U); };

  void g(...);

  template <typename T>
  auto f() { return g(new A(0, 0)); }

  int g(A<int> *);
  void h() { return f<void *>(); }

And another non-dependent one:

  template <typename T, typename U = T> struct A { A(T); };

  void g(...);

  template <typename T>
  auto f() { return g(new A(0)); }

  int g(A<int> *);
  void h() { return f<void *>(); }

And here is an example that is dependent:

  template<class T>
  struct S {
   template<class U = T> struct A { A(int); };

   auto f() { return new A(0); } // dependent return type
  };

Proposed resolution (November, 2022) [SUPERSEDED]:

  1. Change in 7.6.2.8 [expr.new] paragraph 2 as follows:

    If a placeholder type (9.2.9.6 [dcl.spec.auto]) or a placeholder for a deduced class type (9.2.9.7 [dcl.type.class.deduct]) appears in the type-specifier-seq of a new-type-id or type-id of a new-expression, the allocated type is deduced as follows: Let init be the new-initializer , if any, and T be the new-type-id or type-id of the new-expression, then the allocated type is the type deduced for the variable x in the invented declaration (9.2.9.6 [dcl.spec.auto]):
    T x init ;
    
  2. Insert new paragraphs before 13.8.3.2 [temp.dep.type] paragraph 7 and change the latter as follows:

    An initializer is dependent if any constituent expression (6.9.1 [intro.execution]) of the initializer is type-dependent. A placeholder type (9.2.9.6.1 [dcl.spec.auto.general]) is dependent if it designates a type deduced from a dependent initializer.

    A placeholder for a deduced class type (9.2.9.7 [dcl.type.class.deduct]) is dependent if

    • it has a dependent initializer or
    • any default template-argument of the primary class template named by the placeholder is dependent when considered in the scope enclosing the primary class template.

    A type is dependent if it is

    • ...
    • a function type whose exception specification is value-dependent,
    • denoted by a dependent placeholder type,
    • denoted by a dependent placeholder for a deduced class type,
    • ...

Proposed resolution (February, 2023):

  1. Change in 7.6.2.8 [expr.new] paragraph 2 as follows:

    If a placeholder type (9.2.9.6 [dcl.spec.auto]) or a placeholder for a deduced class type (9.2.9.7 [dcl.type.class.deduct]) appears in the type-specifier-seq of a new-type-id or type-id of a new-expression, the allocated type is deduced as follows: Let init be the new-initializer , if any, and T be the new-type-id or type-id of the new-expression, then the allocated type is the type deduced for the variable x in the invented declaration (9.2.9.6 [dcl.spec.auto]):
    T x init ;
    
  2. Insert new paragraphs before 13.8.3.2 [temp.dep.type] paragraph 7 and change the latter as follows:

    An initializer is dependent if any constituent expression (6.9.1 [intro.execution]) of the initializer is type-dependent. A placeholder type (9.2.9.6.1 [dcl.spec.auto.general]) is dependent if it designates a type deduced from a dependent initializer.

    A placeholder for a deduced class type (9.2.9.7 [dcl.type.class.deduct]) is dependent if

    • it has a dependent initializer, or
    • it refers to an alias template that is a member of the current instantiation whose definition is dependent.

    [ Example:

      template<class T, class V>
      struct S { S(T); };
    
      template<class U>
      struct A {
        template<class T> using X = S<T, U>;
        template<class T> using Y = S<T, int>;
        void f() {
          new X(1);    // dependent
          new Y(1);    // not dependent
        }
      };
    

    -- end example ]

    A type is dependent if it is

    • ...
    • a function type whose exception specification is value-dependent,
    • denoted by a dependent placeholder type,
    • denoted by a dependent placeholder for a deduced class type,
    • ...



1602. Linkage of specialization vs linkage of template arguments

Section: 13.9.2  [temp.inst]     Status: review     Submitter: Richard Smith     Date: 2013-01-09

The Standard does not appear to specify the linkage of a template specialization. 13.9.2 [temp.inst] paragraph 11 does say,

Implicitly instantiated class and function template specializations are placed in the namespace where the template is defined.

which could be read as implying that the specialization has the same linkage as the template itself. Implementation practice seems to be that the weakst linkage of the template and the arguments is used for the specialization.

Additional notes (February, 2023)

Template specializations do not have linkage.




2054. Missing description of class SFINAE

Section: 13.10.3  [temp.deduct]     Status: review     Submitter: Ville Voutilainen     Date: 2014-12-07

Presumably something like the following should be well-formed, where a deduction failure in a partial specialization is handled as a SFINAE case as it is with function templates and not a hard error:

  template <class T, class U> struct X   {
    typedef char member;
  };

  template<class T> struct X<T,
   typename enable_if<(sizeof(T)>sizeof(
     float)), float>::type>
  {
    typedef long long member;
  };

  int main() {
    cout << sizeof(X<double, float>::member);
  }

However, this does not appear to be described anywhere in the Standard.

Additional notes (January, 2023)

The section on SFINAE (13.10.3.1 [temp.deduct.general] paragraph 8) is not specific to function templates, and 13.7.6.2 [temp.spec.partial.match] paragraph 2 hands off the "matching" determination for partial specializations to 13.10.3 [temp.deduct] in general. However, the definition of deduction substitution loci in 13.10.3.1 [temp.deduct.general] paragraph 7 does not account for the template argument list of a partial specialization.

Possible resolution:

Change in 13.10.3.1 [temp.deduct.general] paragraph 7 as follows:

The deduction substitution loci are The substitution occurs in all types and expressions that are used in the deduction substitution loci. ...





Issues with "Drafting" Status


369. Are new/delete identifiers or preprocessing-op-or-punc?

Section: 5.4  [lex.pptoken]     Status: drafting     Submitter: Martin v. Loewis     Date: 30 July 2002

5.4 [lex.pptoken] paragraph 2 specifies that there are 5 categories of tokens in phases 3 to 6. With 5.12 [lex.operators] paragraph 1, it is unclear whether new is an identifier or a preprocessing-op-or-punc; likewise for delete. This is relevant to answer the question whether

#define delete foo

is a well-formed control-line, since that requires an identifier after the define token.

(See also issue 189.)




1655. Line endings in raw string literals

Section: 5.4  [lex.pptoken]     Status: drafting     Submitter: Mike Miller     Date: 2013-04-26

According to 5.4 [lex.pptoken] paragraph 3,

If the input stream has been parsed into preprocessing tokens up to a given character:

However, phase 1 is defined as:

Physical source file characters are mapped, in an implementation-defined manner, to the basic source character set (introducing new-line characters for end-of-line indicators) if necessary. The set of physical source file characters accepted is implementation-defined. Trigraph sequences (_N4140_.2.4 [lex.trigraph]) are replaced by corresponding single-character internal representations. Any source file character not in the basic source character set (5.3 [lex.charset]) is replaced by the universal-character-name that designates that character.

The reversion described in 5.4 [lex.pptoken] paragraph 3 specifically does not mention the replacement of physical end-of-line indicators with new-line characters. Is it intended that, for example, a CRLF in the source of a raw string literal is to be represented as a newline character or as the original characters?




1901. punctuator referenced but not defined

Section: 5.6  [lex.token]     Status: drafting     Submitter: Richard Smith     Date: 2014-03-25

The syntactic nonterminal punctuator appears in the grammar for token in 5.6 [lex.token], but it is nowhere defined. It should be merged with operator and given an appropriate list of tokens as a definition for the merged term.

Proposed resolution (October, 2017):

  1. Change 5.5 [lex.digraph] paragraph 2 as follows

  2. In all respects of the language except in an attribute-token (9.12.1 [dcl.attr.grammar]), each alternative token behaves the same, respectively, as its primary token, except for its spelling.18 The set of alternative tokens...
  3. Change the grammar in 5.6 [lex.token] as follows:



  4. Change 5.6 [lex.token] paragraph 1 as follows:

  5. There are five four kinds of tokens: identifiers, keywords, literals,19 operators, and other separators and symbols. Blanks, horizontal and vertical tabs, newlines, formfeeds, and comments (collectively, “white space”), as described below, are ignored except as they serve to separate tokens. [Note: Some white space is required to separate otherwise adjacent identifiers, keywords, numeric literals, and alternative tokens containing alphabetic characters. —end note] Each preprocessing-token resulting from translation phase 6 is converted into the corresponding token as follows:

    [Note: Within an attribute-token (9.12.1 [dcl.attr.grammar]), a token formed from a preprocessing-token that satisfies the syntactic requirements of an identifier is considered to be an identifier with the spelling of the preprocessing-token. —end note]

  6. Delete the final sentence of 5.12 [lex.operators] paragraph 1.

  7. Each preprocessing-op-or-punc is converted to a single token in translation phase 7 (5.2 [lex.phases]).



189. Definition of operator and punctuator

Section: 5.12  [lex.operators]     Status: drafting     Submitter: Mike Miller     Date: 20 Dec 1999

The nonterminals operator and punctuator in 5.6 [lex.token] are not defined. There is a definition of the nonterminal operator in 12.4 [over.oper] paragraph 1, but it is apparent that the two nonterminals are not the same: the latter includes keywords and multi-token operators and does not include the nonoverloadable operators mentioned in paragraph 3.

There is a definition of preprocessing-op-or-punc in 5.12 [lex.operators] , with the notation that

Each preprocessing-op-or-punc is converted to a single token in translation phase 7 (2.1).
However, this list doesn't distinguish between operators and punctuators, it includes digraphs and keywords (can a given token be both a keyword and an operator at the same time?), etc.

Suggested resolution:


  1. Change 12.4 [over.oper] to use the term overloadable-operator.
  2. Change 5.6 [lex.token] to use the term operator-token instead of operator (since there are operators that are keywords and operators that are composed of more than one token).
  3. Change 5.12 [lex.operators] to define the nonterminals operator-token and punctuator.

Additional note (April, 2005):

The resolution for this problem should also address the fact that sizeof and typeid (and potentially others like decltype that may be added in the future) are described in some places as “operators” but are not listed in 12.4 [over.oper] paragraph 3 among the operators that cannot be overloaded.

(See also issue 369.)




1723. Multicharacter user-defined character literals

Section: 5.13.8  [lex.ext]     Status: drafting     Submitter: Mike Miller     Date: 2013-07-31

According to 5.13.3 [lex.ccon] paragraph 1, a multicharacter literal like 'ab' is conditionally-supported and has type int.

According to 5.13.8 [lex.ext] paragraph 6,

If L is a user-defined-character-literal, let ch be the literal without its ud-suffix. S shall contain a literal operator (12.6 [over.literal]) whose only parameter has the type of ch and the literal L is treated as a call of the form

A user-defined-character-literal like 'ab'_foo would thus require a literal operator

However, that is not one of the signatures permitted by 12.6 [over.literal] paragraph 3.

Should multicharacter user-defined-character-literals be conditionally-supported? If so, 12.6 [over.literal] paragraph 3 should be adjusted accordingly. If not, a note in 5.13.8 [lex.ext] paragraph 6 saying explicitly that they are not supported would be helpful.




1735. Out-of-range literals in user-defined-literals

Section: 5.13.8  [lex.ext]     Status: drafting     Submitter: Mike Miller     Date: 2013-08-12

The description of the numeric literals occurring as part of user-defined-integer-literals and user-defined-floating-literals in 5.13.8 [lex.ext] says nothing about whether they are required to satisfy the same constraints as literals that are not part of a user-defined-literal. In particular, because it is the spelling, not the value, of the literal that is used for raw literal operators and literal operator templates, there is no particular reason that they should be restricted to the maximum values and precisions that apply to ordinary literals (and one could imagine that this would be a good notation for allowing literals of extended-precision types).

Is this relaxation of limits intended to be required, or is it a quality-of-implementation issue? Should something be said, either normatively or non-normatively, about this question?




1529. Nomenclature for variable vs reference non-static data member

Section: 6.1  [basic.pre]     Status: drafting     Submitter: Daniel Krügler     Date: 2012-07-24

According to 6.1 [basic.pre] paragraph 6,

A variable is introduced by the declaration of a reference other than a non-static data member or of an object.

In other words, non-static data members of reference type are not variables. This complicates the wording in a number of places, where the text refers to “variable or data member,” presumably to cover the reference case, but that phrasing could lead to the mistaken impression that all data members are not variables. It would be better if either there were a term for the current phrase “variable or data member” or if there were a less-unwieldy term for “non-static data member of reference type” that could be used in place of “data member” in the current phrasing.




1897. ODR vs alternative tokens

Section: 6.3  [basic.def.odr]     Status: drafting     Submitter: Hubert Tong     Date: 2014-03-21

According to 5.5 [lex.digraph] paragraph 2,

In all respects of the language, each alternative token behaves the same, respectively, as its primary token, except for its spelling.

However, the primary and alternative tokens are different tokens, which runs afoul of the ODR requirement in 6.3 [basic.def.odr] paragraph 6 that the definitions consist of the “same sequence of tokens.” This wording should be amended to allow for use of primary and alternative tokens.




2480. Lookup for enumerators in modules

Section: 6.5.1  [basic.lookup.general]     Status: drafting     Submitter: Richard Smith     Date: 2021-02-12

According to 6.5.1 [basic.lookup.general] paragraphs 2-3,

...A declaration X precedes a program point P in a translation unit L if P follows X, X inhabits a class scope and is reachable from P, or else...

A single search in a scope S for a name N from a program point P finds all declarations that precede P to which any name that is the same as N (6.1 [basic.pre]) is bound in S.

These rules cause problems for finding enumerators when qualified by an exported name of its enumeration type, unlike a member of a class. For example:

  export module A;
  enum class X { x };
  enum Y { y };

  export module B;
  import A;
  export using XB = X;
  export using YB = Y;

  // client code
  import B;
  int main() {
    XB x = XB::x; // should be OK because definition of X is reachable, even
                  // though A is not imported
    YB y = YB::y; // similarly OK
    YB z = ::y;   // error, because y from module A is not visible
  }

It would seem that this problem could be addressed by changing “inhabits a class scope” to “does not inhabit a namespace scope.”




1089. Template parameters in member selections

Section: 6.5.5.1  [basic.lookup.qual.general]     Status: drafting     Submitter: Daveed Vandevoorde     Date: 2010-06-29

In an example like

    template<typename T> void f(T p)->decltype(p.T::x);

The nested-name-specifier T:: looks like it refers to the template parameter. However, if this is instantiated with a type like

    struct T { int x; };
    struct S: T { };

the reference will be ambiguous, since it is looked up in both the context of the expression, finding the template parameter, and in the class, finding the base class injected-class-name, and this could be a deduction failure. As a result, the same declaration with a different parameter name

    template<typename U> void f(U p)->decltype(p.U::x);

is, in fact, not a redeclaration because the two can be distinguished by SFINAE.

It would be better to add a new lookup rule that says that if a name in a template definition resolves to a template parameter, that name is not subject to further lookup at instantiation time.

Additional note (November, 2020):

Paper P1787R6, adopted at the November, 2020 meeting, partially addresses this issue.




2324. Size of base class subobject

Section: 6.7.2  [intro.object]     Status: drafting     Submitter: GB     Date: 2017-02-27

P0488R0 comment GB 9

According to 6.7.2 [intro.object] paragraph 7,

Unless it is a bit-field (11.4.10 [class.bit]), a most derived object shall have a nonzero size and shall occupy one or more bytes of storage. Base class subobjects may have zero size.

Base class objects of zero size is a misleading term, as sizeof such an object is non-zero. Size should not be a property of an object, rather of a type.




2325. std::launder and reuse of character buffers

Section: 6.7.2  [intro.object]     Status: drafting     Submitter: CA     Date: 2017-02-27

P0488R0 comment CA 12

The status of the following code should be explicitly indicated in the Standard to avoid surprise:

  #include <new>
  int bar() {
    alignas(int) unsigned char space[sizeof(int)];
    int *pi = new (static_cast<void *>(space)) int;
    *pi = 42;
    return [=]() mutable {
      return   *std::launder(reinterpret_cast<int *>(space)); }();
   }

In particular, it appears that the call to std::launder has undefined behaviour because the captured copy of space is not established to provide storage for an object of type int (sub 6.7.2 [intro.object] paragraph 1). Furthermore, the code has undefined behaviour also because it attempts to access the stored value of the int object through a glvalue of an array type other than one of the ones allowed by sub 7.2.1 [basic.lval] paragraph 8.




2469. Implicit object creation vs constant expressions

Section: 6.7.2  [intro.object]     Status: drafting     Submitter: Hubert Tong     Date: 2020-12-07

It is not intended that implicit object creation, as described in 6.7.2 [intro.object] paragraph 10, should occur during constant expression evaluation, but there is currently no wording prohibiting it.

Notes from the February, 2021 teleconference:

This issue was occasioned by issue 2464, which is also the subject of LWG issue 3495. CWG reviewed the proposed resolution and agrees with it. The intended approach for this issue is to wait for LWG to resolve that issue, then add a note in the core section pointing out the implications of that requirement for implicit object creation.




1027. Type consistency and reallocation of scalar types

Section: 6.7.3  [basic.life]     Status: drafting     Submitter: Gabriel Dos Reis     Date: 2010-02-03

Is the following well-formed?

    int f() {
        int i = 3;
        new (&i) float(1.2);
        return i;
    }

The wording that is intended to prevent such shenanigans, 6.7.3 [basic.life] paragraphs 7-9, doesn't quite apply here. In particular, paragraph 7 reads,

If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:

The problem here is that this wording only applies “after the lifetime of an object has ended and before the storage which the object occupied is reused;” for an object of a scalar type, its lifetime only ends when the storage is reused or released (paragraph 1), so it appears that these restrictions cannot apply to such objects.

(See also issues 1116 and 1338.)

Proposed resolution (August, 2010):

This issue is resolved by the resolution of issue 1116.




1530. Member access in out-of-lifetime objects

Section: 6.7.3  [basic.life]     Status: drafting     Submitter: Howard Hinnant     Date: 2012-07-26

According to 6.7.3 [basic.life] paragraphs 5 and 6, a program has undefined behavior if a pointer or glvalue designating an out-of-lifetime object

is used to access a non-static data member or call a non-static member function of the object

It is not clear what the word “access” means in this context. A reasonable interpretation might be using the pointer or glvalue as the left operand of a class member access expression; alternatively, it might mean to read or write the value of that member, allowing a class member access expression that is used only to form an address or bind a reference.

This needs to be clarified. A relevant consideration is the recent adoption of the resolution of issue 597, which eased the former restriction on simple address manipulations involving out-of-lifetime objects: if base-class offset calculations are now allowed, why not non-static data member offset calculations?

(See also issue 1531 for other uses of the term “access.”)

Additional note (January, 2013):

A related question is the meaning of the phrase “before the constructor begins execution” in 11.9.5 [class.cdtor] paragraph 1 means:

For an object with a non-trivial constructor, referring to any non-static member or base class of the object before the constructor begins execution results in undefined behavior.

For example:

  struct DerivedMember { ... };

  struct Base {
    Base(DerivedMember const&);
  };

  struct Derived : Base {
    DerivedMember x;
    Derived() : Base(x) {}
  };

  Derived a;

Is the reference to Derived::x in the mem-initializer valid?

Additional note (March, 2013):

This clause is phrased in terms of the execution of the constructor. However, it is possible for an aggregate to have a non-trivial default constructor and be initialized without executing a constructor. The wording needs to be updated to allow for non-constructor initialization to avoid appearing to imply undefined behavior for an example like:

  struct X {
    std::string s;
  } x = {};
  std::string t = x.s;  // No constructor called for x: undefined behavior?



1853. Defining “allocated storage”

Section: 6.7.3  [basic.life]     Status: drafting     Submitter: Jeffrey Yasskin     Date: 2014-02-09

The term “allocated storage” is used in several places in the Standard to refer to memory in which an object may be created (dynamic, static, or automatic storage), but it has no formal definition.

See also issue 2551.




1997. Placement new and previous initialization

Section: 6.7.4  [basic.indet]     Status: drafting     Submitter: Jason Merrill     Date: 2014-09-08

Given the following example,

  #include <new>

  int main() {
    unsigned char buf[sizeof(int)] = {};
    int *ip = new (buf) int;
    return *ip; // 0 or undefined?
  }

Should the preceding initializsation of the buffer carry over to the value of *ip? According to 6.7.4 [basic.indet] paragraph 1,

When storage for an object with automatic or dynamic storage duration is obtained, the object has an indeterminate value, and if no initialization is performed for the object, that object retains an indeterminate value until that value is replaced (7.6.19 [expr.ass]).

In this case, no new storage is being obtained for the int object created by the new-expression.




1634. Temporary storage duration

Section: 6.7.5  [basic.stc]     Status: drafting     Submitter: Richard Smith     Date: 2013-03-04

According to 6.7.5 [basic.stc] paragraph 2,

Static, thread, and automatic storage durations are associated with objects introduced by declarations (6.2 [basic.def]) and implicitly created by the implementation (6.7.7 [class.temporary]).

The apparent intent of the reference to 6.7.7 [class.temporary] is that a temporary whose lifetime is extended to be that of a reference with one of those storage durations is considered also to have that storage duration. This interpretation is buttressed by use of the phrase “an object with the same storage duration as the temporary” (twice) in 6.7.7 [class.temporary] paragraph 5.

There are two problems, however: first, the specification of lifetime extension of temporaries (also in 6.7.7 [class.temporary] paragraph 5) does not say anything about storage duration. Also, nothing is said in either of these locations about the storage duration of a temporary whose lifetime is not extended.

The latter point is important because 6.7.3 [basic.life] makes a distinction between the lifetime of an object and the acquisition and release of the storage the object occupies, at least for objects with non-trivial initialization and/or a non-trivial destructor. The assumption is made in 6.7.7 [class.temporary] and elsewhere that the storage in which a temporary is created is no longer available for reuse, as specified in 6.7.3 [basic.life], after the lifetime of the temporary has ended, but this assumption is not explicitly stated. One way to make that assumption explicit would be to define a storage duration for temporaries whose lifetime is not extended.

See also issues 365 and 2256.




2533. Storage duration of implicitly created objects

Section: 6.7.5  [basic.stc]     Status: drafting     Submitter: Andrey Erokhin     Date: 2022-02-17

In subclause 6.7.2 [intro.object] paragraph 10, operations implicitly creating objects are defined:

Some operations are described as implicitly creating objects within a specified region of storage. For each operation that is specified as implicitly creating objects, that operation implicitly creates and starts the lifetime of zero or more objects of implicit-lifetime types (6.8.1 [basic.types.general]) in its specified region of storage if...

However, the standard does not specify the storage duration that such an implicitly-created object has; this new method of object creation is not mentioned in 6.7.5.1 [basic.stc.general] paragraph 2:

Static, thread, and automatic storage durations are associated with objects introduced by declarations (6.2 [basic.def]) and implicitly created by the implementation (6.7.7 [class.temporary]). The dynamic storage duration is associated with objects created by a new-expression (7.6.2.8 [expr.new]).

With the exception of malloc, the storage duration should probably be that of the object providing storage (if any), similar to the provision for subobjects in 6.7.5.6 [basic.stc.inherit]:

The storage duration of subobjects and reference members is that of their complete object (6.7.2 [intro.object]).

The storage duration of an object created by a non-allocating form of an allocation function (17.6.3.4 [new.delete.placement]) should be treated similarly.

Possible resolution:

  1. Change in 6.7.2 [intro.object] paragraph 13 as follows:

    Any implicit or explicit invocation of a function named operator new or operator new[] implicitly creates objects with dynamic storage duration in the returned region of storage and returns a pointer to a suitable created object.
  2. No change in 6.7.5.1 [basic.stc.general] paragraph 2:

    Static, thread, and automatic storage durations are associated with objects introduced by declarations (6.2 [basic.def]) and implicitly created by the implementation (6.7.7 [class.temporary]). The dynamic storage duration is associated with objects created by a new-expression (7.6.2.8 [expr.new]).
  3. Change in 6.7.5.6 [basic.stc.inherit] paragraph 1 as follows:

    The storage duration of subobjects and reference members is that of their complete object. The storage duration of an object nested within another object x is the storage duration of x (6.7.2 [intro.object]).
  4. Change in 7.6.2.8 [expr.new] paragraph 9 as follows:

    An object created by a new-expression that invokes an allocation function with a non-allocating form (see below) has the storage duration of the object that used to occupy the region of storage where the new object is created. Objects Any other object created by a new-expression have has dynamic storage duration (6.7.5.5 [basic.stc.dynamic]). [Note 5: The lifetime of such an object is not necessarily restricted to the scope in which it is created. —end note]
  5. Change in 20.2.12 [c.malloc] paragraph 4 as follows:

    These functions implicitly create objects (6.7.2 [intro.object]) with dynamic storage duration in the returned region of storage and return a pointer to a suitable created object. In the case of calloc and realloc, the objects are created before the storage is zeroed or copied, respectively.



1676. auto return type for allocation and deallocation functions

Section: 6.7.5.5.2  [basic.stc.dynamic.allocation]     Status: drafting     Submitter: Richard Smith     Date: 2013-05-04

Do we need explicit language to forbid auto as the return type of allocation and deallocation functions?

(See also issue 1669.)




2073. Allocating memory for exception objects

Section: 6.7.5.5.2  [basic.stc.dynamic.allocation]     Status: drafting     Submitter: Jonathan Wakely     Date: 2015-01-20

According to 6.7.5.5.2 [basic.stc.dynamic.allocation] paragraph 4,

[Note: In particular, a global allocation function is not called to allocate storage for objects with static storage duration (6.7.5.2 [basic.stc.static]), for objects or references with thread storage duration (6.7.5.3 [basic.stc.thread]), for objects of type std::type_info (7.6.1.8 [expr.typeid]), or for an exception object (14.2 [except.throw]). —end note]

The restriction against allocating exception objects on the heap was intended to ensure that heap exhaustion could be reported by throwing an exception, i.e., that obtaining storage for std::bad_alloc could not fail because the heap was full. However, this implicitly relied on the assumption of a single thread and does not scale to large numbers of threads, so the restriction should be lifted and another mechanism found for guaranteeing the ability to throw std::bad_alloc.

Notes from the February, 2016 meeting:

The prohibition of using an allocation function appears only in a note, although there is a normative reference to the rule in 14.2 [except.throw] paragraph 4. CWG was in favor of retaining the prohibition of using a C++ allocation function for the memory of an exception object, with the implicit understanding that use of malloc would be permitted. The resolution for this issue should delete the note and move the prohibition to normative text in the relevant sections.




2042. Exceptions and deallocation functions

Section: 6.7.5.5.3  [basic.stc.dynamic.deallocation]     Status: drafting     Submitter: Richard Smith     Date: 2014-11-13

According to 6.7.5.5.3 [basic.stc.dynamic.deallocation] paragraph 3,

If a deallocation function terminates by throwing an exception, the behavior is undefined.

This seems to be in conflict with the provisions of 14.5 [except.spec]: if a deallocation function throws an exception that is not allowed by its exception-specification, 14.5 [except.spec] paragraph 10 would appear to give the program defined behavior (calling std::unexpected() or std::terminate()). (Note that 14.5 [except.spec] paragraph 18 explicitly allows an explicit exception-specification for a deallocation function.)




1211. Misaligned lvalues

Section: 6.7.6  [basic.align]     Status: drafting     Submitter: David Svoboda     Date: 2010-10-20

6.7.6 [basic.align] speaks of “alignment requirements,” and 6.7.5.5.2 [basic.stc.dynamic.allocation] requires the result of an allocation function to point to “suitably aligned” storage, but there is no explicit statement of what happens when these requirements are violated (presumably undefined behavior).




1701. Array vs sequence in object representation

Section: 6.8  [basic.types]     Status: drafting     Submitter: Lawrence Crowl     Date: 2013-06-14

According to 6.8 [basic.types] paragraph 4,

The object representation of an object of type T is the sequence of N unsigned char objects taken up by the object of type T, where N equals sizeof(T).

However, it is not clear that a “sequence” can be indexed, as an array can and as is required for the implementation of memcpy and similar code.

Additional note, November, 2014:

An additional point of concern has been raised as to whether it is appropriate to refer to the constituent bytes of an object as being “objects” themselves, along with the interaction of this specification with copying or not copying parts of the object representation that do not participate in the value representation of the object (“padding” bytes).




1986. odr-use and delayed initialization

Section: 6.9.3.2  [basic.start.static]     Status: drafting     Submitter: Richard Smith     Date: 2014-08-21

The current wording of 6.9.3.2 [basic.start.static] allows deferral of static and thread_local initialization until a variable or function in the containing translation unit is odr-used. This requires implementations to avoid optimizing away the relevant odr-uses. We should consider relaxing the rule to allow for such optimizations.

Proposed resolution (November, 2014):

For a variable V with thread or static storage duration, let X be the set of all variables with the same storage duration as V that are defined in the same translation unit as V. If the observable behavior of the abstract machine (6.7.2 [intro.object]) depends on the value of V through an evaluation E, and E is not sequenced before the end of the initialization of any variable in X, then the end of the initialization of all variables in X is sequenced before E.

There is also a problem (submitted by David Majnemer) if the odr-use occurs in a constexpr context that does not require the variable to be constructed. For example,

  struct A { A(); };
  thread_local A a;

  constexpr bool f() { return &a != nullptr; }

It doesn't seem possible to construct a before its odr-use in f.

There is implementation divergence in the handling of this example.

Notes from the November, 2014 meeting:

CWG determined that the second part of the issue (involving constexpr) is not a defect because the address of an object with thread storage duration is not a constant expression.

Additional note, May, 2015:

CWG failed to indicate where and how to apply the wording in the proposed resolution. In addition, further review has raised concern that “sequenced before” may be the wrong relation to use for the static storage duration case because it implies “in the same thread.”

Notes from the October, 2015 meeting:

The suggested wording is intended to replace some existing wording in 6.9.3.2 [basic.start.static] paragraph 2. CWG affirmed that the correct relationship is “happens before” and not “sequenced before.”




2148. Thread storage duration and order of initialization

Section: 6.9.3.2  [basic.start.static]     Status: drafting     Submitter: Hubert Tong     Date: 2015-06-22

The terms “ordered” and “unordered” initialization are only defined in 6.9.3.2 [basic.start.static] paragraph 2 for entities with static storage duration. They should presumably apply to entities with thread storage duration as well.




2444. Constant expressions in initialization odr-use

Section: 6.9.3.3  [basic.start.dynamic]     Status: drafting     Submitter: Davis Herring     Date: 2019-11-06

According to 6.9.3.3 [basic.start.dynamic] paragraph 3,

A non-initialization odr-use is an odr-use (6.3 [basic.def.odr]) not caused directly or indirectly by the initialization of a non-local static or thread storage duration variable.

Paragraphs 4-6 uses this term to exclude such odr-uses from consideration in determining the point by which a deferred initialization must be performed. A static_assert or a template argument expression can odr-use a variable, but it cannot be said to define any time during execution.

Suggestion: Add constant expression evaluation to the definition. Rename the term to “initializing odr-use” (based on effect rather than cause). Add a note saying that no such odr-use can occur before main begins.

Notes from the February, 2021 teleconference:

CWG agreed with the direction.




2503. Unclear relationship among name, qualified name, and unqualified name

Section: 7.5.4  [expr.prim.id]     Status: drafting     Submitter: Jens Maurer     Date: 2021-08-04

The phrases “name”, “qualified name” and “unqualified name” are used in various places. It is not clear that all names are either one or the other; there could, in fact, be a third kind of name that is neither.

See also editorial issue 4793.




2473. Parentheses in pseudo-destructor calls

Section: 7.5.4.4  [expr.prim.id.dtor]     Status: drafting     Submitter: Mike Miller     Date: 2020-12-15

According to 7.5.4.4 [expr.prim.id.dtor] paragraph 2,

If the id-expression names a pseudo-destructor, T shall be a scalar type and the id-expression shall appear as the right operand of a class member access (7.6.1.5 [expr.ref]) that forms the postfix-expression of a function call (7.6.1.3 [expr.call]).

This would appear to make the following example ill-formed, because it is the parenthesized expression and not the class member access that is the postfix-expression in the function call:

  typedef int T;
  void f(int* p) {
    (p->~T)();   // Ill-formed?
  }

Presumably this is an oversight.




2542. Is a closure type a structural type?

Section: 7.5.5.2  [expr.prim.lambda.closure]     Status: drafting     Submitter: Zhihao Yuan     Date: 2022-03-01

Consider:

  template <auto V>
  void foo() {}

  void bar() {
    foo<[i = 3] { return i; }>();
  }

It is unclear whether the data members of a closure type are public or private. This makes a difference, since it affects whether a closure type is a structural type or not (13.2 [temp.param] paragraph 7:

A structural type is one of the following:

Possible resolution:

Change in 7.5.5.2 [expr.prim.lambda.closure] paragraph 2 as follows:

... The closure type is not an aggregate type (9.4.2 [dcl.init.aggr]) and not a structural type (13.2 [temp.param]). ...



2086. Reference odr-use vs implicit capture

Section: 7.5.5.3  [expr.prim.lambda.capture]     Status: drafting     Submitter: Hubert Tong     Date: 2015-02-14

Whether a reference is odr-used or not has less to do with the context where it is named and more to do with its initializer. In particular, 7.5.5 [expr.prim.lambda] bullet 12.2 leads to cases where references that can never be odr-used are implicitly captured:

A lambda-expression with an associated capture-default that does not explicitly capture this or a variable with automatic storage duration (this excludes any id-expression that has been found to refer to an init-capture's associated non-static data member), is said to implicitly capture the entity (i.e., this or a variable) if the compound-statement:

For example, ref should not be captured in the following:

  struct A {
    A() = default;
    A(const A &) = delete;
  } globalA;

  constexpr bool bar(int &, const A &a) { return &a == &globalA; }

  int main() {
    A &ref = globalA;
    [=](auto q) { static_assert(bar(q, ref), ""); }(0);
  }



1521. T{expr} with reference types

Section: 7.6.1.4  [expr.type.conv]     Status: drafting     Submitter: Steve Adamczyk     Date: 2012-07-10

According to 7.6.1.4 [expr.type.conv] paragraph 4,

Similarly, a simple-type-specifier or typename-specifier followed by a braced-init-list creates a temporary object of the specified type direct-list-initialized (9.4.5 [dcl.init.list]) with the specified braced-init-list, and its value is that temporary object as a prvalue.

This wording does not handle the case where T is a reference type: it is not possible to create a temporary object of that type, and presumably the result would be an xvalue, not a prvalue.




2283. Missing complete type requirements

Section: 7.6.1.4  [expr.type.conv]     Status: drafting     Submitter: Richard Smith     Date: 2016-06-27

P0135R1 (Wording for guaranteed copy elision through simplified value categories) removes complete type requirements from 7.6.1.3 [expr.call] (under the assumption that subclause 9.4 [dcl.init] has them; apparently it does not) and from 7.6.1.8 [expr.typeid] paragraph 3. These both appear to be bad changes and should presumably be reverted.




2557. Class member access referring to an unrelated class

Section: 7.6.1.5  [expr.ref]     Status: drafting     Submitter: Jens Maurer     Date: 2022-03-25

Consider:

  struct A {
    static int x;
  };

  struct B {
    using type = A;
  };

  int y = B().type::x;

There seems to be no requirement that the member named in a class member access actually is a member of the class of the object expression. Subclause 7.5.4.1 [expr.prim.id.general] paragraph 3 does not cover static members:

An id-expression that denotes a non-static data member or non-static member function of a class can only be used:

Suggested resolution:

  1. Change in 7.6.1.5 [expr.ref] paragraph 4 as follows:

    Otherwise, the object expression shall be of class type. The class type shall be complete unless the class member access appears in the definition of that class.
    [Note: The program is ill-formed if the result differs from that when the class is complete (6.5.2 [class.member.lookup]). —end note]
    [Note: 6.5.5 [basic.lookup.qual] describes how names are looked up after the . and -> operators. —end note] If E2 is a qualified-id, the terminal name of its nested-name-specifier shall denote the type of E1 or a base class thereof.

    [Example:

      struct A {
        static int x;
      };
    
      struct B {
        static int x;
      };
    
      struct D : B {
        using type = A;
      };
    
      int y1 = D().B::x;         // OK, B is a base class of D
      int y2 = D().type::x;      // error: A is not a base class of D
      int y3 = D::type::x;       // OK, evaluates A::x
    

    end example ]

  2. Change in 7.6.1.5 [expr.ref] bullet 6.5 as follows:

  3. Change in 7.5.4.1 [expr.prim.id.general] paragraph 3 as follows:

    An id-expression that denotes a non-static data member or non-static member function of a class can only be used:
    • as part of a class member access (7.6.1.5 [expr.ref]) in which the object expression refers to the member's class [ Footnote: ... ] or a class derived from that class, or
    • to form a pointer to member (7.6.2.2 [expr.unary.op]), or
    • if that id-expression denotes a non-static data member and it appears in an unevaluated operand.



1965. Explicit casts to reference types

Section: 7.6.1.7  [expr.dynamic.cast]     Status: drafting     Submitter: Richard Smith     Date: 2014-07-07

The specification of dynamic_cast in 7.6.1.7 [expr.dynamic.cast] paragraph 2 (and const_cast in 7.6.1.11 [expr.const.cast] is the same) says that the operand of a cast to an lvalue reference type must be an lvalue, so that

  struct A { virtual ~A(); }; A &&make_a();

  A &&a = dynamic_cast<A&&>(make_a());   // ok
  const A &b = dynamic_cast<const A&>(make_a()); // ill-formed

The behavior of static_cast is an odd hybrid:

  struct B : A { }; B &&make_b();
  A &&c = static_cast<A&&>(make_b()); // ok
  const A &d = static_cast<const A&>(make_b()); // ok
  const B &e = static_cast<const B&>(make_a()); // ill-formed

(Binding a const lvalue reference to an rvalue is permitted by 7.6.1.9 [expr.static.cast] paragraph 4 but not by paragraphs 2 and 3.)

There is implementation divergence on the treatment of these examples.

Also, const_cast permits binding an rvalue reference to a class prvalue but not to any other kind of prvalue, which seems like an unnecessary restriction.

Finally, 7.6.1.9 [expr.static.cast] paragraph 3 allows binding an rvalue reference to a class or array prvalue, but not to other kinds of prvalues; those are covered in paragraph 4. This would be less confusing if paragraph 3 only dealt with binding rvalue references to glvalues and left all discussion of prvalues to paragraph 4, which adequately handles the class and array cases as well.

Notes from the May, 2015 meeting:

CWG reaffirmed the status quo for dynamic_cast but felt that const_cast should be changed to permit binding an rvalue reference to types that have associated memory (class and array types).




2243. Incorrect use of implicit conversion sequence

Section: 7.6.1.9  [expr.static.cast]     Status: drafting     Submitter: Hubert Tong     Date: 2016-03-08

The term “implicit conversion sequence” is now used in some non-call contexts (e.g., 7.6.1.9 [expr.static.cast] paragraph 4, 7.6.16 [expr.cond] paragraph 4, 7.6.10 [expr.eq] paragraph 4) ) and it is not clear that the current definition is suited for these additional uses. In particular, passing an argument in a function call is always copy-initialization, but some of these contexts require consideration of direct-initialization.

Notes from the December, 2016 teleconference:

The problem is that overload resolution relies on copy initalization and thus does not describe direct initialization. See also issue 1781.




232. Is indirection through a null pointer undefined behavior?

Section: 7.6.2.2  [expr.unary.op]     Status: drafting     Submitter: Mike Miller     Date: 5 Jun 2000

At least a couple of places in the IS state that indirection through a null pointer produces undefined behavior: 6.9.1 [intro.execution] paragraph 4 gives "dereferencing the null pointer" as an example of undefined behavior, and 9.3.4.3 [dcl.ref] paragraph 4 (in a note) uses this supposedly undefined behavior as justification for the nonexistence of "null references."

However, 7.6.2.2 [expr.unary.op] paragraph 1, which describes the unary "*" operator, does not say that the behavior is undefined if the operand is a null pointer, as one might expect. Furthermore, at least one passage gives dereferencing a null pointer well-defined behavior: 7.6.1.8 [expr.typeid] paragraph 2 says

If the lvalue expression is obtained by applying the unary * operator to a pointer and the pointer is a null pointer value (7.3.12 [conv.ptr]), the typeid expression throws the bad_typeid exception (17.7.5 [bad.typeid]).

This is inconsistent and should be cleaned up.

Bill Gibbons:

At one point we agreed that dereferencing a null pointer was not undefined; only using the resulting value had undefined behavior.

For example:

    char *p = 0;
    char *q = &*p;

Similarly, dereferencing a pointer to the end of an array should be allowed as long as the value is not used:

    char a[10];
    char *b = &a[10];   // equivalent to "char *b = &*(a+10);"

Both cases come up often enough in real code that they should be allowed.

Mike Miller:

I can see the value in this, but it doesn't seem to be well reflected in the wording of the Standard. For instance, presumably *p above would have to be an lvalue in order to be the operand of "&", but the definition of "lvalue" in 7.2.1 [basic.lval] paragraph 2 says that "an lvalue refers to an object." What's the object in *p? If we were to allow this, we would need to augment the definition to include the result of dereferencing null and one-past-the-end-of-array.

Tom Plum:

Just to add one more recollection of the intent: I was very happy when (I thought) we decided that it was only the attempt to actually fetch a value that creates undefined behavior. The words which (I thought) were intended to clarify that are the first three sentences of the lvalue-to-rvalue conversion, 7.3.2 [conv.lval]:

An lvalue (7.2.1 [basic.lval]) of a non-function, non-array type T can be converted to an rvalue. If T is an incomplete type, a program that necessitates this conversion is ill-formed. If the object to which the lvalue refers is not an object of type T and is not an object of a type derived from T, or if the object is uninitialized, a program that necessitates this conversion has undefined behavior.

In other words, it is only the act of "fetching", of lvalue-to-rvalue conversion, that triggers the ill-formed or undefined behavior. Simply forming the lvalue expression, and then for example taking its address, does not trigger either of those errors. I described this approach to WG14 and it may have been incorporated into C 1999.

Mike Miller:

If we admit the possibility of null lvalues, as Tom is suggesting here, that significantly undercuts the rationale for prohibiting "null references" -- what is a reference, after all, but a named lvalue? If it's okay to create a null lvalue, as long as I don't invoke the lvalue-to-rvalue conversion on it, why shouldn't I be able to capture that null lvalue as a reference, with the same restrictions on its use?

I am not arguing in favor of null references. I don't want them in the language. What I am saying is that we need to think carefully about adopting the permissive approach of saying that it's all right to create null lvalues, as long as you don't use them in certain ways. If we do that, it will be very natural for people to question why they can't pass such an lvalue to a function, as long as the function doesn't do anything that is not permitted on a null lvalue.

If we want to allow &*(p=0), maybe we should change the definition of "&" to handle dereferenced null specially, just as typeid has special handling, rather than changing the definition of lvalue to include dereferenced nulls, and similarly for the array_end+1 case. It's not as general, but I think it might cause us fewer problems in the long run.

Notes from the October 2003 meeting:

See also issue 315, which deals with the call of a static member function through a null pointer.

We agreed that the approach in the standard seems okay: p = 0; *p; is not inherently an error. An lvalue-to-rvalue conversion would give it undefined behavior.

Proposed resolution (October, 2004):

(Note: the resolution of issue 453 also resolves part of this issue.)

  1. Add the indicated words to 7.2.1 [basic.lval] paragraph 2:

    An lvalue refers to an object or function or is an empty lvalue (7.6.2.2 [expr.unary.op]).
  2. Add the indicated words to 7.6.2.2 [expr.unary.op] paragraph 1:

    The unary * operator performs indirection: the expression to which it is applied shall be a pointer to an object type, or a pointer to a function type and the result is an lvalue referring to the object or function to which the expression points, if any. If the pointer is a null pointer value (7.3.12 [conv.ptr]) or points one past the last element of an array object (7.6.6 [expr.add]), the result is an empty lvalue and does not refer to any object or function. An empty lvalue is not modifiable. If the type of the expression is “pointer to T,” the type of the result is “T.” [Note: a pointer to an incomplete type (other than cv void) can be dereferenced. The lvalue thus obtained can be used in limited ways (to initialize a reference, for example); this lvalue must not be converted to an rvalue, see 7.3.2 [conv.lval].—end note]
  3. Add the indicated words to 7.3.2 [conv.lval] paragraph 1:

    If the object to which the lvalue refers is not an object of type T and is not an object of a type derived from T, or if the object is uninitialized, or if the lvalue is an empty lvalue (7.6.2.2 [expr.unary.op]), a program that necessitates this conversion has undefined behavior.
  4. Change 6.9.1 [intro.execution] as indicated:

    Certain other operations are described in this International Standard as undefined (for example, the effect of dereferencing the null pointer division by zero).

Note (March, 2005):

The 10/2004 resolution interacts with the resolution of issue 73. We added wording to 6.8.4 [basic.compound] paragraph 3 to the effect that a pointer containing the address one past the end of an array is considered to “point to” another object of the same type that might be located there. The 10/2004 resolution now says that it would be undefined behavior to use such a pointer to fetch the value of that object. There is at least the appearance of conflict here; it may be all right, but it at needs to be discussed further.

Notes from the April, 2005 meeting:

The CWG agreed that there is no contradiction between this direction and the resolution of issue 73. However, “not modifiable” is a compile-time concept, while in fact this deals with runtime values and thus should produce undefined behavior instead. Also, there are other contexts in which lvalues can occur, such as the left operand of . or .*, which should also be restricted. Additional drafting is required.

(See also issue 1102.)




901. Deleted operator delete

Section: 7.6.2.8  [expr.new]     Status: drafting     Submitter: John Spicer     Date: 20 May, 2009

It is not clear from 7.6.2.8 [expr.new] whether a deleted operator delete is referenced by a new-expression in which there is no initialization or in which the initialization cannot throw an exception, rendering the program ill-formed. (The question also arises as to whether such a new-expression constitutes a “use” of the deallocation function in the sense of 6.3 [basic.def.odr].)

Notes from the July, 2009 meeting:

The rationale for defining a deallocation function as deleted would presumably be to prevent such objects from being freed. Treating the new-expression as a use of such a deallocation function would mean that such objects could not be created in the first place. There is already an exemption from freeing an object if “a suitable deallocation function [cannot] be found;” a deleted deallocation function should be treated similarly.




2102. Constructor checking in new-expression

Section: 7.6.2.8  [expr.new]     Status: drafting     Submitter: Richard Smith     Date: 2015-03-16

According to 7.6.2.8 [expr.new] paragraph 19,

If the new-expression creates an object or an array of objects of class type, access and ambiguity control are done for the allocation function, the deallocation function (11.4.11 [class.free]), and the constructor (11.4.5 [class.ctor]).

The mention of “the constructor” here is strange. For the “object of class type” case, access and ambiguity control are done when we perform initialization in paragraph 17, and we might not be calling a constructor anyway (for aggregate initialization). This seems wrong.

For the “array of objects of class type” case, it makes slightly more sense (we need to check the trailing array elements can be default-initialized) but again (a) we aren't necessarily using a constructor, (b) we should say which constructor — and we may need overload resolution to find it, and (c) shouldn't this be part of initialization, so we can distinguish between the cases where we should copy-initialize from {} and the cases where we should default-initialize?




2281. Consistency of aligned operator delete replacement

Section: 7.6.2.8  [expr.new]     Status: drafting     Submitter: Richard Smith     Date: 2016-06-27

We should require that a program that replaces the aligned form of operator delete also replaces the sized+aligned form. We only allow a program to replace the non-sized form without replacing the sized form for backwards compatibility. This is not needed for the alignment feature, which is new.

Notes from the March, 2018 meeting:

CWG concurred with the recommendation.




2623. Invoking destroying operator delete for constructor failure

Section: 7.6.2.8  [expr.new]     Status: drafting     Submitter: Blacktea Hamburger     Date: 2022-08-25

Subclause 7.6.2.8 [expr.new] paragraph 28 specifies the lookup for the deallocation function that is invoked when the construction of the object in a new-expression exits via an exception. However, a destroying operator delete (6.7.5.5.3 [basic.stc.dynamic.deallocation]) should never be used, because the object in question has not yet been fully created.

Suggested resolution [SUPERSEDED]:

Change in 7.6.2.8 [expr.new] paragraph 28 as follows:

A declaration of a placement deallocation function matches the declaration of a placement allocation function if it has the same number of parameters and, after parameter transformations (9.3.4.6 [dcl.fct]), all parameter types except the first are identical. If the lookup finds a single matching deallocation function, that function will be called; otherwise, no deallocation function will be called. If the lookup finds a usual deallocation function and that function, considered as a placement deallocation function, would have been selected as a match for the allocation function, the program is ill-formed. For a non-placement allocation function, the normal deallocation function lookup is used to find the matching deallocation function (7.6.2.9 [expr.delete]) , except that any destroying operator delete (6.7.5.5.3 [basic.stc.dynamic.deallocation]) is ignored.



2013. Pointer subtraction in large array

Section: 7.6.6  [expr.add]     Status: drafting     Submitter: Jason Merrill     Date: 2014-10-02

The common code sequence used by most implementations for pointer subtraction involves subtracting the pointer values to determine the number of bytes and then shifting to scale for the size of the array element. This produces incorrect results when the difference in bytes is larger than can be represented by a ptrdiff_t. For example, assuming a 32-bit ptrdiff_t:

  int *a, *b;
  a = malloc(0x21000000 * sizeof(int));
  b = a + 0x21000000;
  printf("%lx\n", (long)(b - a));

This will typically print e1000000 instead of 21000000.

Getting the right answer would require using a more expensive code sequence. It would be better to make this undefined behavior.




2182. Pointer arithmetic in array-like containers

Section: 7.6.6  [expr.add]     Status: drafting     Submitter: Jonathan Wakely     Date: 2015-10-20

The current direction for issue 1776 (see paper P0137) calls into question the validity of doing pointer arithmetic to address separately-allocated but contiguous objects in a container like std::vector. A related question is whether there should be some allowance made for allowing pointer arithmetic using a pointer to a base class if the derived class is a standard-layout class with no non-static data members. It is possible that std::launder could play a part in the resolution of this issue.

Notes from the February, 2016 meeting:

This issue is expected to be resolved by the resolution of issue 1776. The major problem is when the elements of the vector contain constant or reference members; 6.7.3 [basic.life] paragraph 7 implies that pointer arithmetic leading to such an object produces undefined behavior, and CWG expects this to continue. Some changes to the interface of std::vector may be required, perhaps using std::launder as part of iterator processing.




2023. Composite reference result type of conditional operator

Section: 7.6.16  [expr.cond]     Status: drafting     Submitter: Daniel Krügler     Date: 2014-10-16

The conditional operator converts pointer operands to their composite pointer type (7.6.16 [expr.cond] bullets 6.3 and 6.4). Similar treatment should be afforded to operands of reference type.

See also issue 2018.




2316. Simplifying class conversions in conditional expressions

Section: 7.6.16  [expr.cond]     Status: drafting     Submitter: S. B. Tam     Date: 2016-08-16

According to 7.6.16 [expr.cond] paragraph 4,

Attempts are made to form an implicit conversion sequence from an operand expression E1 of type T1 to a target type related to the type T2 of the operand expression E2 as follows:

It seems that to satisfy the conditions in the first two sub-bullets, T2 must be a class type, in which case T2 is the same as the type described in the third sub-bullet, since the lvalue-to-rvalue conversion does not change types and the other two conversions do not apply to a class type. Thus, this bullet and sub-bullets could be simplified to:

Notes from the August, 2020 teleconference:

This issue and suggested resolution predate the resolution of issue 2321, which added the second sub-bullet (the citation above reflects the wording after adoption of issue 2321), giving the result the cv-qualification of T1 instead of that of T2. The suggested resolution would revert that accepted resolution.




1542. Compound assignment of braced-init-list

Section: 7.6.19  [expr.ass]     Status: drafting     Submitter: Mike Miller     Date: 2012-08-21

The specification of 7.6.19 [expr.ass] paragraph 9 is presumably intended to allow use of a braced-init-list as the operand of a compound assignment operator as well as a simple assignment operator, although the normative wording does not explicitly say so. (The example in that paragraph does include

  complex<double> z;
  z += { 1, 2 };      // meaning z.operator+=({1,2})

for instance, which could be read to imply compound assignment operators for scalar types as well.)

However, the details of how this is to be implemented are not clear. Paragraph 7 says,

The behavior of an expression of the form E1 op = E2 is equivalent to E1 = E1 op E2 except that E1 is evaluated only once.

Applying this pattern literally to a braced-init-list yields invalid code: x += {1} would become x = x + {1}, which is non-syntactic.

Another problem is how to apply the prohibition against narrowing conversions to a compound assignment. For example,

  char c;
  c += {1};

would presumably always be a narrowing error, because after integral promotions, the type of c+1 is int. The similar issue 1078 was classified as "NAD" because the workaround was simply to add a cast to suppress the error; however, there is no place to put a similar cast in a compound assignment.

Notes from the October, 2012 meeting:

The incorrect description of the meaning of a compound assignment with a braced-init-list should be fixed by CWG. The question of whether it makes sense to apply narrowing rules to such assignments is better addressed by EWG.

See also issue 2399.




2166. Unclear meaning of “undefined constexpr function”

Section: 7.7  [expr.const]     Status: drafting     Submitter: Howard Hinnant     Date: 2015-08-05

According to 7.7 [expr.const] bullet 2.3, an expression is a constant expression unless (among other reasons) it would evaluate

This does not address the question of the point at which a constexpr function must be defined. The intent, in order to allow mutually-recursive constexpr functions, was that the function must be defined prior to the outermost evaluation that eventually results in the invocation, but this is not clearly stated.




2186. Unclear point that “preceding initialization” must precede

Section: 7.7  [expr.const]     Status: drafting     Submitter: Hubert Tong     Date: 2015-10-24

Similar to the concern of issue 2166, the requirement of 7.7 [expr.const] bullet 2.7.1 for

does not specify the point at which the determination of “preceding initialization” is made: is it at the point at which the reference to the variable appears lexically, or is it the point at which the outermost constant evaluation occurs? There is implementation divergence on this point.




1680. Including <initializer_list> for range-based for

Section: 8.6.5  [stmt.ranged]     Status: drafting     Submitter: Richard Smith     Date: 2013-05-13

A simple example like

  int main() {
    int k = 0;
    for (auto x : { 1, 2, 3 })
      k += x;
    return k;
  }

requires that the <initializer_list> header be included, because the expansion of the range-based for involves a declaration of the form

  auto &&__range = { 1, 2, 3 };

and a braced-init-list causes auto to be deduced as a specialization of std::initializer_list. This seems unnecessary and could be eliminated by specifying that __range has an array type for cases like this.

(It should be noted that EWG is considering a proposal to change auto deduction for cases involving braced-init-lists, so resolution of this issue should be coordinated with that effort.)

Notes from the September, 2013 meeting:

CWG felt that this issue should be resolved by using the array variant of the range-based for implementation.




2115. Order of implicit destruction vs release of automatic storage

Section: 8.7  [stmt.jump]     Status: drafting     Submitter: Richard Smith     Date: 2015-04-16

The relative ordering between destruction of automatic variables on exit from a block and the release of the variables' storage is not specified by the Standard: are all the destructors executed first and then the storage released, or are they interleaved?

Notes from the February, 2016 meeting:

CWG agreed that the storage should persist until all destructions are complete, although the “as-if” rule would allow for unobservable optimizations of this ordering.




1223. Syntactic disambiguation and trailing-return-types

Section: 8.9  [stmt.ambig]     Status: drafting     Submitter: Michael Wong     Date: 2010-11-08

Because the restriction that a trailing-return-type can appear only in a declaration with “the single type-specifier auto” (9.3.4.6 [dcl.fct] paragraph 2) is a semantic, not a syntactic, restriction, it does not influence disambiguation, which is “purely syntactic” (8.9 [stmt.ambig] paragraph 3). Consequently, some previously unambiguous expressions are now ambiguous. For example:

struct A {
  A(int *);
  A *operator()(void);
  int B;
};

int *p;
typedef struct BB { int C[2]; } *B, C;

void foo() {
// The following line becomes invalid under C++0x:
  A (p)()->B;  // ill-formed function declaration

// In the following,
// - B()->C is either type-id or class member access expression
// - B()->C[1] is either type-id or subscripting expression
// N3126 subclause 8.2 [dcl.ambig.res] does not mention an ambiguity
// with these forms of expression
  A a(B ()->C);  // function declaration or object declaration
  sizeof(B ()->C[1]);  // sizeof(type-id) or sizeof on an expression
}

Notes from the March, 2011 meeting:

CWG agreed that the presence of auto should be considered in disambiguation, even though it is formally handled semantically rather than syntactically.




2117. Explicit specializations and constexpr function templates

Section: 9.2.6  [dcl.constexpr]     Status: drafting     Submitter: Faisal Vali     Date: 2015-04-26

According to 9.2.6 [dcl.constexpr] paragraph 6,

If no specialization of the template would satisfy the requirements for a constexpr function or constexpr constructor when considered as a non-template function or constructor, the template is ill-formed; no diagnostic required.

This should say “instantiated template specialization” instead of just “specialization” to clarify that an explicit specialization is not in view here.




1348. Use of auto in a trailing-return-type

Section: 9.2.9.6  [dcl.spec.auto]     Status: drafting     Submitter: Richard Smith     Date: 2011-08-16

It is not clear whether the auto specifier can appear in a trailing-return-type.




1670. auto as conversion-type-id

Section: 9.2.9.6  [dcl.spec.auto]     Status: drafting     Submitter: Richard Smith     Date: 2013-04-26

N3690 comment FI 4

The current wording allows something like

  struct S {
    operator auto() { return 0; }
  } s;

If it is intended to be permitted, the details of its handling are not clear. Also, a similar syntax has been discussed as a possible future extension for dealing with proxy types in deduction which, if adopted, could cause confusion.

Additional note, November, 2013:

Doubt was expressed during the 2013-11-25 drafting review teleconference as to the usefulness of this provision. It is therefore being left open for further consideration after C++14 is finalized.

Notes from the February, 2014 meeting:

CWG continued to express doubt as to the usefulness of this construct but felt that if it is permitted, the rules need clarification.

Additional note (December, 2021):

See duplicate issue 2493 for additional details.




1868. Meaning of “placeholder type”

Section: 9.2.9.6  [dcl.spec.auto]     Status: drafting     Submitter: Dawn Perchik     Date: 2014-02-13

9.2.9 [dcl.type] paragraph 2 describes the auto specifier as “a placeholder for a type to be deduced.” Elsewhere, the Standard refers to the type represented by the auto specifier as a “placeholder type.” This usage has been deemed confusing by some, requiring either a definition of one or both terms or rewording to avoid them.




1488. abstract-pack-declarators in type-ids

Section: 9.3.2  [dcl.name]     Status: drafting     Submitter: Richard Smith     Date: 2012-03-28

The grammar for type-id in 11.3 [class.name] paragraph 1 has two problems. First, the fact that we allow an abstract-pack-declarator makes some uses of type-id (template arguments, alignment specifiers, exception-specifications) ambiguous: T... could be parsed either as a type-id, including the ellipsis, or as the type-id T with a following ellipsis. There does not appear to be any rule to disambiguate these parses.

The other problem is that we do not allow parentheses in an abstract-pack-declarator, which makes

  template<typename...Ts> void f(Ts (&...)[4]);

ill-formed because (&...)() is not an abstract-pack-declarator. There is implementation variance on this point.




453. References may only bind to “valid” objects

Section: 9.3.4.3  [dcl.ref]     Status: drafting     Submitter: Gennaro Prota     Date: 18 Jan 2004

9.3.4.3 [dcl.ref] paragraph 4 says:

A reference shall be initialized to refer to a valid object or function. [Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the "object" obtained by dereferencing a null pointer, which causes undefined behavior ...]

What is a "valid" object? In particular the expression "valid object" seems to exclude uninitialized objects, but the response to Core Issue 363 clearly says that's not the intent. This is an example (overloading construction on constness of *this) by John Potter, which I think is supposed to be legal C++ though it binds references to objects that are not initialized yet:

 struct Fun {
    int x, y;
    Fun (int x, Fun const&) : x(x), y(42) { }
    Fun (int x, Fun&) : x(x), y(0) { }
  };
  int main () {
    const Fun f1 (13, f1);
    Fun f2 (13, f2);
    cout << f1.y << " " << f2.y << "\n";
  }

Suggested resolution: Changing the final part of 9.3.4.3 [dcl.ref] paragraph 4 to:

A reference shall be initialized to refer to an object or function. From its point of declaration on (see 6.4.2 [basic.scope.pdecl]) its name is an lvalue which refers to that object or function. The reference may be initialized to refer to an uninitialized object but, in that case, it is usable in limited ways (6.7.3 [basic.life], paragraph 6) [Note: On the other hand, a declaration like this:
    int & ref = *(int*)0;
is ill-formed because ref will not refer to any object or function ]

I also think a "No diagnostic is required." would better be added (what about something like int& r = r; ?)

Proposed Resolution (October, 2004):

(Note: the following wording depends on the proposed resolution for issue 232.)

Change 9.3.4.3 [dcl.ref] paragraph 4 as follows:

A reference shall be initialized to refer to a valid object or function. If an lvalue to which a reference is directly bound designates neither an existing object or function of an appropriate type (9.4.4 [dcl.init.ref]), nor a region of memory of suitable size and alignment to contain an object of the reference's type (6.7.2 [intro.object], 6.7.3 [basic.life], 6.8 [basic.types]), the behavior is undefined. [Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” empty lvalue obtained by dereferencing a null pointer, which causes undefined behavior. As does not designate an object or function. Also, as described in 11.4.10 [class.bit], a reference cannot be bound directly to a bit-field. ]

The name of a reference shall not be used in its own initializer. Any other use of a reference before it is initialized results in undefined behavior. [Example:

  int& f(int&);
  int& g();

  extern int& ir3;
  int* ip = 0;

  int& ir1 = *ip;     // undefined behavior: null pointer
  int& ir2 = f(ir3);  // undefined behavior: ir3 not yet initialized
  int& ir3 = g();
  int& ir4 = f(ir4);  // ill-formed: ir4 used in its own initializer
end example]

Rationale: The proposed wording goes beyond the specific concerns of the issue. It was noted that, while the current wording makes cases like int& r = r; ill-formed (because r in the initializer does not "refer to a valid object"), an inappropriate initialization can only be detected, if at all, at runtime and thus "undefined behavior" is a more appropriate treatment. Nevertheless, it was deemed desirable to continue to require a diagnostic for obvious compile-time cases.

It was also noted that the current Standard does not say anything about using a reference before it is initialized. It seemed reasonable to address both of these concerns in the same wording proposed to resolve this issue.

Notes from the April, 2005 meeting:

The CWG decided that whether to require an implementation to diagnose initialization of a reference to itself should be handled as a separate issue (504) and also suggested referring to “storage” instead of “memory” (because 6.7.2 [intro.object] defines an object as a “region of storage”).

Proposed Resolution (April, 2005):

(Note: the following wording depends on the proposed resolution for issue 232.)

Change 9.3.4.3 [dcl.ref] paragraph 4 as follows:

A reference shall be initialized to refer to a valid object or function. If an lvalue to which a reference is directly bound designates neither an existing object or function of an appropriate type (9.4.4 [dcl.init.ref]), nor a region of storage of suitable size and alignment to contain an object of the reference's type (6.7.2 [intro.object], 6.7.3 [basic.life], 6.8 [basic.types]), the behavior is undefined. [Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” empty lvalue obtained by dereferencing a null pointer, which causes undefined behavior. As does not designate an object or function. Also, as described in 11.4.10 [class.bit], a reference cannot be bound directly to a bit-field. ]

Any use of a reference before it is initialized results in undefined behavior. [Example:

  int& f(int&);
  int& g();

  extern int& ir3;
  int* ip = 0;

  int& ir1 = *ip;     // undefined behavior: null pointer
  int& ir2 = f(ir3);  // undefined behavior: ir3 not yet initialized
  int& ir3 = g();
  int& ir4 = f(ir4);  // undefined behavior: ir4 used in its own initializer
end example]

Note (February, 2006):

The word “use” in the last paragraph of the proposed resolution was intended to refer to the description in 6.3 [basic.def.odr] paragraph 2. However, that section does not define what it means for a reference to be “used,” dealing only with objects and functions. Additional drafting is required to extend 6.3 [basic.def.odr] paragraph 2 to apply to references.

Additional note (May, 2008):

The proposed resolution for issue 570 adds wording to define “use” for references.

Note, January, 2012:

The resolution should also probably deal with the fact that the “one-past-the-end” address of an array does not designate a valid object (even if such a pointer might “point to” an object of the correct type, per 6.8.4 [basic.compound]) and thus is not suuitable for the lvalue-to-rvalue conversion.




1001. Parameter type adjustment in dependent parameter types

Section: 9.3.4.6  [dcl.fct]     Status: drafting     Submitter: Jason Merrill     Date: 2009-11-08

According to 9.3.4.6 [dcl.fct] paragraph 5, top-level cv-qualifiers on parameter types are deleted when determining the function type. It is not clear how or whether this adjustment should be applied to parameters of function templates when the parameter has a dependent type, however. For example:

    template<class T> struct A {
       typedef T arr[3];
    };

    template<class T> void f(const typename A<T>::arr) { } // #1

    template void f<int>(const A<int>::arr);

    template <class T> struct B {
       void g(T);
    };

    template <class T> void B<T>::g(const T) { } // #2

If the const in #1 is dropped, f<int> has a parameter type of A* rather than the const A* specified in the explicit instantiation. If the const in #2 is not dropped, we fail to match the definition of B::g to its declaration.

Rationale (November, 2010):

The CWG agreed that this behavior is intrinsic to the different ways cv-qualification applies to array types and non-array types.

Notes, January, 2012:

Additional discussion of this issue arose regarding the following example:

    template<class T> struct A {
      typedef double Point[2];
      virtual double calculate(const Point point) const = 0;
    };

    template<class T> struct B : public A<T> {
      virtual double calculate(const typename A<T>::Point point) const {
        return point[0];
      }
    };

    int main() {
      B<int> b;
      return 0;
    }

The question is whether the member function in B<int> has the same type as that in A<int>: is the parameter-type-list instantiated directly (i.e., using the adjusted types) or regenerated from the individual parameter types?

(See also issue 1322.)




1668. Parameter type determination still not clear enough

Section: 9.3.4.6  [dcl.fct]     Status: drafting     Submitter: Daniel Krügler     Date: 2013-04-25

According to 9.3.4.6 [dcl.fct] paragraph 5,

The type of a function is determined using the following rules. The type of each parameter (including function parameter packs) is determined from its own decl-specifier-seq and declarator. After determining the type of each parameter, any parameter of type “array of T” or “function returning T” is adjusted to be “pointer to T” or “pointer to function returning T,” respectively. After producing the list of parameter types, any top-level cv-qualifiers modifying a parameter type are deleted when forming the function type. The resulting list of transformed parameter types and the presence or absence of the ellipsis or a function parameter pack is the function's parameter-type-list. [Note: This transformation does not affect the types of the parameters. For example, int(*)(const int p, decltype(p)*) and int(*)(int, const int*) are identical types. —end note]

This is not sufficiently clear to specify the intended handling of an example like

  void f(int a[10], decltype(a) *p );

Should the type of p be int(*)[10] or int**? The latter is the intended result, but the phrase “after determining the type of each parameter” makes it sound as if the adjustments are performed after all the parameter types have been determined from the decl-specifier-seq and declarator instead of for each parameter individually.

See also issue 1444.




2537. Overbroad grammar for parameter-declaration

Section: 9.3.4.6  [dcl.fct]     Status: drafting     Submitter: Davis Herring     Date: 2021-02-25

9.3.4.6 [dcl.fct] paragraph 3 specifies the grammar for parameter-declaration:

  parameter-declaration:
      attribute-specifier-seqopt thisopt decl-specifier-seq declarator
      attribute-specifier-seqopt thisopt decl-specifier-seq declarator = initializer-clause
      attribute-specifier-seqopt thisopt decl-specifier-seq abstract-declaratoropt
      attribute-specifier-seqopt thisopt decl-specifier-seq abstract-declaratoropt = initializer-clause

This is overly permissive; using a defining-type-specifier-seq instead of a decl-specifier-seq is sufficient.

Proposed resolution (November, 2022):

  1. Change in 9.2.2 [dcl.stc] paragraph 4 as follows:

    There can be no static function declarations within a block, nor any static function parameters.
  2. Change in 9.2.2 [dcl.stc] paragraph 5 as follows:

    The extern specifier shall not be used in the declaration of a class member or function parameter.
  3. Change in 9.2.4 [dcl.typedef] paragraph 1 as follows:

    The typedef specifier shall not be combined in a decl-specifier-seq with any other kind of specifier except a defining-type-specifier, and it shall not be used in the decl-specifier-seq of a parameter-declaration (9.3.4.6 [dcl.fct]) nor in the decl-specifier-seq of a function-definition (9.5 [dcl.fct.def]).
  4. Change in 9.2.8 [dcl.inline] paragraph 4 as follows:

    The inline specifier shall not appear on a block scope declaration or on the declaration of a function parameter.
  5. Change in 9.3.4.6 [dcl.fct] paragraph 3 as follows:

      parameter-declaration:
          attribute-specifier-seqopt thisopt decl-specifier-seq defining-type-specifier-seq declarator
          attribute-specifier-seqopt thisopt decl-specifier-seq defining-type-specifier-seq declarator = initializer-clause
          attribute-specifier-seqopt thisopt decl-specifier-seq defining-type-specifier-seq abstract-declaratoropt
          attribute-specifier-seqopt thisopt decl-specifier-seq defining-type-specifier-seq abstract-declaratoropt = initializer-clause
    

CWG 2023-02-07

Additional drafting is needed to address references to decl-specifier-seq in other parts of the standard. A list is here. Furthermore, reducing the grammar to a type-specifier-seq appears to be sufficient.




325. When are default arguments parsed?

Section: 9.3.4.7  [dcl.fct.default]     Status: drafting     Submitter: Nathan Sidwell     Date: 27 Nov 2001

The standard is not precise enough about when the default arguments of member functions are parsed. This leads to confusion over whether certain constructs are legal or not, and the validity of certain compiler implementation algorithms.

9.3.4.7 [dcl.fct.default] paragraph 5 says "names in the expression are bound, and the semantic constraints are checked, at the point where the default argument expression appears"

However, further on at paragraph 9 in the same section there is an example, where the salient parts are

  int b;
  class X {
    int mem2 (int i = b); // OK use X::b
    static int b;
  };
which appears to contradict the former constraint. At the point the default argument expression appears in the definition of X, X::b has not been declared, so one would expect ::b to be bound. This of course appears to violate 6.4.7 [basic.scope.class] paragraph 1(2) "A name N used in a class S shall refer to the same declaration in its context and when reevaluated in the complete scope of S. No diagnostic is required."

Furthermore 6.4.7 [basic.scope.class] paragraph 1(1) gives the scope of names declared in class to "consist not only of the declarative region following the name's declarator, but also of .. default arguments ...". Thus implying that X::b is in scope in the default argument of X::mem2 previously.

That previous paragraph hints at an implementation technique of saving the token stream of a default argument expression and parsing it at the end of the class definition (much like the bodies of functions defined in the class). This is a technique employed by GCC and, from its behaviour, in the EDG front end. The standard leaves two things unspecified. Firstly, is a default argument expression permitted to call a static member function declared later in the class in such a way as to require evaluation of that function's default arguments? I.e. is the following well formed?

  class A {
    static int Foo (int i = Baz ());
    static int Baz (int i = Bar ());
    static int Bar (int i = 5);
 };
If that is well formed, at what point does the non-sensicalness of
  class B {
    static int Foo (int i = Baz ());
    static int Baz (int i = Foo());
  };
become detected? Is it when B is complete? Is it when B::Foo or B::Baz is called in such a way to require default argument expansion? Or is no diagnostic required?

The other problem is with collecting the tokens that form the default argument expression. Default arguments which contain template-ids with more than one parameter present a difficulty in determining when the default argument finishes. Consider,

  template <int A, typename B> struct T { static int i;};
  class C {
    int Foo (int i = T<1, int>::i);
  };
The default argument contains a non-parenthesized comma. Is it required that this comma is seen as part of the default argument expression and not the beginning of another of argument declaration? To accept this as part of the default argument would require name lookup of T (to determine that the '<' was part of a template argument list and not a less-than operator) before C is complete. Furthermore, the more pathological
  class D {
    int Foo (int i = T<1, int>::i);
    template <int A, typename B> struct T {static int i;};
  };
would be very hard to accept. Even though T is declared after Foo, T is in scope within Foo's default argument expression.

Suggested resolution:

Append the following text to 9.3.4.7 [dcl.fct.default] paragraph 8.

The default argument expression of a member function declared in the class definition consists of the sequence of tokens up until the next non-parenthesized, non-bracketed comma or close parenthesis. Furthermore such default argument expressions shall not require evaluation of a default argument of a function declared later in the class.

This would make the above A, B, C and D ill formed and is in line with the existing compiler practice that I am aware of.

Notes from the October, 2005 meeting:

The CWG agreed that the first example (A) is currently well-formed and that it is not unreasonable to expect implementations to handle it by processing default arguments recursively.

Additional notes, May, 2009:

Presumably the following is ill-formed:

    int f(int = f());

However, it is not clear what in the Standard makes it so. Perhaps there needs to be a statement to the effect that a default argument only becomes usable after the complete declarator of which it is a part.

Notes from the August, 2011 meeting:

In addition to default arguments, commas in template argument lists also cause problems in initializers for nonstatic data members:

    struct S {
      int n = T<a,b>(c);  // ill-formed declarator for member b
                          // or template argument?
    };

(This is from #16 of the IssuesFoundImplementingC0x.pdf document on the Bloomington wiki.

Additional notes (August, 2011):

See also issues 1352 and 361.

Notes from the February, 2012 meeting:

It was decided to handle the question of parsing an initializer like T<a,b>(c) (a template-id or two declarators) in this issue and the remaining questions in issue 361. For this issue, a template-id will only be recognized if there is a preceding declaration of a template.

Additional note (November, 2020):

Paper P1787R6, adopted at the November, 2020 meeting, partially addresses this issue.




1580. Default arguments in explicit instantiations

Section: 9.3.4.7  [dcl.fct.default]     Status: drafting     Submitter: Daveed Vandevoorde     Date: 2012-10-29

It is not clear, either from 9.3.4.7 [dcl.fct.default] or 13.9.3 [temp.explicit], whether it is permitted to add a default argument in an explicit instantiation of a function template:

  template<typename T> void f(T, int) { }
  template void f<int>(int, int=0);  // Permitted?

Notes from the April, 2013 meeting:

The intent is to prohibit default arguments in explicit instantiations.




2327. Copy elision for direct-initialization with a conversion function

Section: 9.4  [dcl.init]     Status: drafting     Submitter: Richard Smith     Date: 2016-09-30

Consider an example like:

  struct Cat {};
  struct Dog { operator Cat(); };

  Dog d;
  Cat c(d);

This goes to 9.4 [dcl.init] bullet 17.6.2:

Otherwise, if the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. The applicable constructors are enumerated (12.2.2.4 [over.match.ctor]), and the best one is chosen through overload resolution (12.2 [over.match]). The constructor so selected is called to initialize the object, with the initializer expression or expression-list as its argument(s). If no constructor applies, or the overload resolution is ambiguous, the initialization is ill-formed.

Overload resolution selects the move constructor of Cat. Initializing the Cat&& parameter of the constructor results in a temporary, per 9.4.4 [dcl.init.ref] bullet 5.2.1.2. This precludes the possitiblity of copy elision for this case.

This seems to be an oversight in the wording change for guaranteed copy elision. We should presumably be simultaneously considering both constructors and conversion functions in this case, as we would for copy-initialization, but we'll need to make sure that doesn't introduce any novel problems or ambiguities.

See also issue 2311.




2128. Imprecise rule for reference member initializer

Section: 9.4.2  [dcl.init.aggr]     Status: drafting     Submitter: Richard Smith     Date: 2015-05-19

According to 11.9.3 [class.base.init] paragraph 11,

A temporary expression bound to a reference member from a brace-or-equal-initializer is ill-formed. [Example:

  struct A {
    A() = default;          // OK
    A(int v) : v(v) { }     // OK
    const int& v = 42;      // OK
  };
  A a1;                     // error: ill-formed binding of temporary to reference
  A a2(1);                  // OK, unfortunately

end example]

The rule is intended to apply only if an actual initialization results in such a binding, but it could be read as applying to the declaration of A::v itself. It would be clearer if the restriction were moved into bullet 9.1, e.g.,




2149. Brace elision and array length deduction

Section: 9.4.2  [dcl.init.aggr]     Status: drafting     Submitter: Vinny Romano     Date: 2015-06-25

According to 9.4.2 [dcl.init.aggr] paragraph 4,

An array of unknown size initialized with a brace-enclosed initializer-list containing n initializer-clauses, where n shall be greater than zero, is defined as having n elements (9.3.4.5 [dcl.array]).

However, the interaction of this with brace elision is not clear. For instance, in the example in paragraph 7,

  struct X { int i, j, k = 42; };
  X a[] = { 1, 2, 3, 4, 5, 6 };
  X b[2] = { { 1, 2, 3 }, { 4, 5, 6 } };

a and b are said to have the same value, even though there are six initializer-clauses in the initializer list in a's initializer and two in b's initializer.

Similarly, 13.10.3.2 [temp.deduct.call] paragraph 1 says,

in the P'[N] case, if N is a non-type template parameter, N is deduced from the length of the initializer list

Should that take into account the underlying type of the array? For example,

  template<int N> void f1(const X(&)[N]);
  f1({ 1, 2, 3, 4, 5, 6 }); // Is N deduced to 2 or 6?

  template<int N> void f2(const X(&)[N][2]);
  f2({ 1, 2, 3, 4, 5, 6 }); // Is N deduced to 1 or 6?



1304. Omitted array bound with string initialization

Section: 9.4.3  [dcl.init.string]     Status: drafting     Submitter: Nikolay Ivchenkov     Date: 2011-04-26

The example in 9.4.3 [dcl.init.string] paragraph 1 says,

  char msg[] = "Syntax error on line %s\n";

shows a character array whose members are initialized with a string-literal. Note that because '\n' is a single character and because a trailing '\0' is appended, sizeof(msg) is 25.

However, there appears to be no normative specification of how the size of the array is to be calculated.




1414. Binding an rvalue reference to a reference-unrelated lvalue

Section: 9.4.4  [dcl.init.ref]     Status: drafting     Submitter: Mike Miller     Date: 2011-11-09

Currently an attempt to bind an rvalue reference to a reference-unrelated lvalue succeeds, binding the reference to a temporary initialized from the lvalue by copy-initialization. This appears to be intentional, as the accompanying example contains the lines

    int i3 = 2;
    double&& rrd3 = i3;  // rrd3 refers to temporary with value 2.0

This violates the expectations of some who expect that rvalue references can be initialized only with rvalues. On the other hand, it is parallel with the handling of an lvalue reference-to-const (and is handled by the same wording). It also can add efficiency without requiring existing code to be rewritten: the implicitly-created temporary can be moved from, just as if the call had been rewritten to create a prvalue temporary from the lvalue explicitly.

On a related note, assuming the binding is permitted, the intent of the overload tiebreaker found in 12.2.4.3 [over.ics.rank] paragraph 3 is not clear:

At question is what “to an rvalue” means here. If it is referring to the value category of the initializer itself, before conversions, then the supposed performance advantage of the binding under discussion does not occur because the competing rvalue and lvalue reference overloads will be ambiguous:

    void f(int&&);    // #1
    void f(const int&);
    void g(double d) {
        f(d);         // ambiguous: #1 does not bind to an rvalue
    }

On the other hand, if “to an rvalue” refers to the actual object to which the reference is bound, i.e., to the temporary in the case under discussion, the phrase would seem to be vacuous because an rvalue reference can never bind directly to an lvalue.

Notes from the February, 2012 meeting:

CWG agreed that the binding rules are correct, allowing creation of a temporary when binding an rvalue reference to a non-reference-related lvalue. The phrase “to an rvalue” in 12.2.4.3 [over.ics.rank] paragraph 3 is a leftover from before binding an rvalue reference to an lvalue was prohibited and should be removed. A change is also needed to handle the following case:

    void f(const char (&)[1]);         // #1
    template<typename T> void f(T&&);  // #2
    void g() {
      f("");                           //calls #2, should call #1
    }

Additional note (October, 2012):

Removing “to an rvalue,” as suggested, would have the effect of negating the preference for binding a function lvalue to an lvalue reference instead of an rvalue reference because the case would now fall under the preceding bullet of 12.2.4.3 [over.ics.rank] bullet 3.1, sub-bullets 4 and 5:

Two implicit conversion sequences of the same form are indistinguishable conversion sequences unless one of the following rules applies:

Presumably if the suggested resolution is adopted, the order of these two bullets should be inverted.




1827. Reference binding with ambiguous conversions

Section: 9.4.4  [dcl.init.ref]     Status: drafting     Submitter: Hubert Tong     Date: 2014-01-07

In the following case,

  struct A {
    operator int &&() const;
    operator int &&() volatile;
    operator long();
  };

  int main() {
    int &&x = A();
  }

the conversion for direct binding cannot be used because of the ambiguity, so indirect binding is used, which allows the use of the conversion to long in creating the temporary.

Is this intended? There is implementation variation.

Notes from the February, 2014 meeting:

CWG agreed that an ambiguity like this should make the initialization ill-formed instead of falling through to do indirect binding.




2018. Qualification conversion vs reference binding

Section: 9.4.4  [dcl.init.ref]     Status: drafting     Submitter: Richard Smith     Date: 2014-10-07

Qualification conversions are not considered when doing reference binding, which leads to some unexpected results:

  template<typename T> T make();
  struct B {}; struct D : B {};

  const int *p1 = make<int*>();           // ok, qualification conversion
  const int *const *p2 = make<int**>();   // ok, qualification conversion
  const int **p3 = make<int**>();         // error, not type safe

  const int &r1 = make<int&>();           // ok, binds directly
  const int *const &r2 = make<int*&>();   // weird, binds to a temporary
  const int *&r3 = make<int*&>();         // error

  const int &&x1 = make<int&&>();         // ok, binds directly
  const int *const &&x2 = make<int*&&>(); // weird, binds to a temporary
  const int *&&x3 = make<int*&&>();       // weird, binds to a temporary

It might make sense to say that similar types are reference-related and if there is a qualification conversion they are reference-compatible.

See also issue 2023.




1996. Reference list-initialization ignores conversion functions

Section: 9.4.5  [dcl.init.list]     Status: drafting     Submitter: Richard Smith     Date: 2014-09-04

The specification for list-initialization of a reference does not consider the existence of conversion functions. Consequently, the following example is ill-formed:

  struct S { operator struct D &(); } s;
  D &d{s};



2144. Function/variable declaration ambiguity

Section: 9.5.1  [dcl.fct.def.general]     Status: drafting     Submitter: Richard Smith     Date: 2015-06-19

The following fragment,

  int f() {};

is syntactically ambiguous. It could be either a function-definition followed by an empty-declaration, or it could be a simple-declaration whose init-declarator has the brace-or-equal-initializer {}. The same is true of a variable declaration

  int a {};

since function-definition simply uses the term declarator in its production.




1854. Disallowing use of implicitly-deleted functions

Section: 9.5.2  [dcl.fct.def.default]     Status: drafting     Submitter: Richard Smith     Date: 2014-02-11

The resolution of issue 1778 means that whether an explicitly-defaulted function is deleted or not cannot be known until the end of the class definition. As a result, new rules are required to disallow references (in, e.g., decltype) to explicitly-defaulted functions that might later become deleted.

Notes from the June, 2014 meeting:

The approach favored by CWG was to make any reference to an explicitly-defaulted function ill-formed if it occurs prior to the end of the class definition.




1485. Out-of-class definition of member unscoped opaque enumeration

Section: 9.7.1  [dcl.enum]     Status: drafting     Submitter: Richard Smith     Date: 2012-03-26

The scope in which the names of enumerators are entered for a member unscoped opaque enumeration is not clear. According to 9.7.1 [dcl.enum] paragraph 10,

Each enum-name and each unscoped enumerator is declared in the scope that immediately contains the enum-specifier.

In the case of a member opaque enumeration defined outside its containing class, however, it is not clear whether the enumerator names are declared in the class scope or in the lexical scope containing the definition. Declaring them in the class scope would be a violation of 11.4 [class.mem] paragraph 1:

The member-specification in a class definition declares the full set of members of the class; no member can be added elsewhere.

Declaring the names in the lexical scope containing the definition would be contrary to the example in 13.7.2.6 [temp.mem.enum] paragraph 1:

  template<class T> struct A {
    enum E : T;
  };
  A<int> a;
  template<class T> enum A<T>::E : T { e1, e2 };
  A<int>::E e = A<int>::e1;

There also appear to be problems with the rules for dependent types and members of the current instantiation.

Notes from the October, 2012 meeting:

CWG agreed that an unscoped opaque enumeration in class scope should be forbidden.




2131. Ambiguity with opaque-enum-declaration

Section: 9.7.1  [dcl.enum]     Status: drafting     Submitter: Richard Smith     Date: 2015-05-28

The declaration

  enum E;

is ambiguous: it could be either a simple-declaration comprising the elaborated-type-specifier enum E and no init-declarator-list, or it could be an opaque-enum-declaration with an omitted enum-base (both of which are ill-formed, for different reasons).

(See also issue 2363.)




2505. Nested unnamed namespace of inline unnamed namespace

Section: 9.8.2.2  [namespace.unnamed]     Status: drafting     Submitter: Nathan Sidwell     Date: 2021-11-22

According to 9.8.2.2 [namespace.unnamed] paragraph 1,

An unnamed-namespace-definition behaves as if it were replaced by

where inline appears if and only if it appears in the unnamed-namespace-definition and all occurrences of unique in a translation unit are replaced by the same identifier, and this identifier differs from all other identifiers in the translation unit.

The use of a single identifier for all occurrences of unique within a translation unit leads to problems when an inline unnamed namespace contains a nested unnamed namespace, e.g.,

    inline namespace {
      namespace { }
    }

In this case, the unnamed namespace cannot be reopened because the lookup for unique finds both the outer and inner namespaces and is thus ambiguous.

Suggested resolution:

Change 9.8.2.2 [namespace.unnamed] paragraph 1 as follows:

...where inline appears if and only if it appears in the unnamed-namespace-definition and all occurrences of unique in each scope in a translation unit are replaced by the same scope-specific identifier, and this identifier differs from all other identifiers in the translation unit.

Notes from the December, 2021 teleconference:

The suggested resolution deals specifically with unnamed namespaces, but there are related examples that do not involve unnamed namespaces. The problem needs to be solved more generally in the specification of lookup.




1817. Linkage specifications and nested scopes

Section: 9.11  [dcl.link]     Status: drafting     Submitter: Richard Smith     Date: 2013-12-04

According to 9.1 [dcl.pre] paragraph 2,

Unless otherwise stated, utterances in Clause 9 [dcl.dcl] about components in, of, or contained by a declaration or subcomponent thereof refer only to those components of the declaration that are not nested within scopes nested within the declaration.

This contradicts the intent of 9.11 [dcl.link] paragraph 4, which says,

In a linkage-specification, the specified language linkage applies to the function types of all function declarators, function names with external linkage, and variable names with external linkage declared within the linkage-specification.

Also, one of the comments in the example in paragraph 4 is inconsistent with the intent:

  extern "C" {
    static void f4(); // the name of the function f4 has
                      // internal linkage (not C language
                      // linkage) and the function's type
                      // has C language linkage.
  }

  extern "C" void f5() {
    extern void f4(); // OK: Name linkage (internal)
                      // and function type linkage (C
                      // language linkage) gotten from
                      // previous declaration.
  }

The language linkage for the block-scope declaration of f4 is presumably determined by the fact that it appears in a C-linkage function, not by the previous declaration.

Proposed resolution (February, 2014):

Change 9.11 [dcl.link] paragraph 4 as follows:

Linkage specifications nest. When linkage specifications nest, the innermost one determines the language linkage. A linkage specification does not establish a scope. A linkage-specification shall occur only in namespace scope (6.4 [basic.scope]). In a linkage-specification, the specified language linkage applies to the function types of all function declarators, function names with external linkage, and variable names with external linkage declared within the linkage-specification, including those appearing in scopes nested inside the linkage specification and not inside a nested linkage-specification. [Example:

...

  extern "C" {
    static void f4(); // the name of the function f4 has
                      // internal linkage (not C language
                      // linkage) and the function's type
                      // has C language linkage.
  }

  extern "C" void f5() {
    extern void f4(); // OK: Name linkage (internal)
                      // and function type linkage (C
                      // language linkage) gotten from
                      // previous declaration.; function type
                      // linkage (C language
                      // linkage) gotten
                      // from linkage specification
  }

Additional note, November, 2014:

The issue has been returned to "drafting" status to clarify the circumstances under which a preceding declaration supplies the language linkage for a declaration (for example, not when the declaration uses a typedef, which carries the language linkage, but only when the declaration uses a function declarator).




1706. alignas pack expansion syntax

Section: 9.12.1  [dcl.attr.grammar]     Status: drafting     Submitter: Daveed Vandevoorde     Date: 2013-06-26

The grammar for alignment-specifier in 9.12.1 [dcl.attr.grammar] paragraph 1 is:

where the ellipsis indicates pack expansion. Naively, one would expect that the expansion would result in forms like

    alignas()
    alignas(1, 2)
    alignas(int, double)

but none of those forms is given any meaning by the current wording. Instead, 13.7.4 [temp.variadic] paragraph 4 says,

In an alignment-specifier (9.12.2 [dcl.align]); the pattern is the alignment-specifier without the ellipsis.

Presumably this means that something like alignas(T...) would expand to something like

    alignas(int) alignas(double)

This is counterintuitive and should be reexamined.

See also messages 24016 through 24021.

Notes from the February, 2014 meeting:

CWG decided to change the pack expansion of alignas so that the type-id or assignment-expression is repeated inside the parentheses and to change the definition of alignas to accept multiple arguments with the same meaning as multiple alignas specifiers.




2223. Multiple alignas specifiers

Section: 9.12.2  [dcl.align]     Status: drafting     Submitter: Mike Herrick     Date: 2016-01-12

According to 9.12.2 [dcl.align] paragraph 4,

The alignment requirement of an entity is the strictest non-zero alignment specified by its alignment-specifiers, if any; otherwise, the alignment-specifiers have no effect.

It is not clear whether this applies to specifiers within a single declaration, or if it is intended to apply to the union of all declarations.

Similarly, paragraph 6 says,

If the defining declaration of an entity has an alignment-specifier, any non-defining declaration of that entity shall either specify equivalent alignment or have no alignment-specifier. Conversely, if any declaration of an entity has an alignment-specifier, every defining declaration of that entity shall specify an equivalent alignment. No diagnostic is required if declarations of an entity have different alignment-specifiers in different translation units.

This only talks about agreement between definitions and non-defining declarations. What about an example where an entity is not defined but is declared with different alignment-specifiers?

  struct alignas(16) A;
  struct alignas(32) A;

If A is not defined, is this, or should it be, ill-formed?

Notes from the February, 2017 meeting:

CWG agreed that the intent of the wording is that the “strictest” requirement is intended to apply to a single declaration, and the requirement for compatibility should apply to all declarations, whether the entity is defined or not.




2607. Visibility of enumerator names

Section: 10.2  [module.interface]     Status: drafting     Submitter: Richard Smith     Date: 2022-06-28

Consider:

  // module interface unit
  export module M;
  export enum E : int;
  enum E : int { e };

  // other translation unit
  import M;
  auto a = E::e;  // #1: OK?
  auto b = e;     // #2: OK?

It is unclear whether the enumerator name e is or ought to be visible in the other translation unit.

See also issues 2588 (friend declarations) and 2480.

CWG 2022-11-10

See 10.2 [module.interface] paragraph 7.




1890. Member type depending on definition of member function

Section: 11.4  [class.mem]     Status: drafting     Submitter: Hubert Tong     Date: 2014-03-07

Consider an example like:

  struct A {
    struct B {
      auto foo() { return 0; }
    };
    decltype(B().foo()) x;
  };

There does not appear to be a prohibition of cases like this, where the type of a member depends on the definition of a member function.

(See also issues 1360, 1397, and 2335.)

Additional notes (January, 2023):

The following example might be related:

  #include <type_traits>

  struct Bar {
    struct Baz {
      int a = 0;
    };
    static_assert(std::is_default_constructible_v<Baz>);
  };



1623. Deleted default union constructor and member initializers

Section: 11.4.5  [class.ctor]     Status: drafting     Submitter: Vinny Romano     Date: 2013-02-15

According to 11.4.5 [class.ctor] paragraph 5,

A defaulted default constructor for class X is defined as deleted if:

Because the presence of a non-static data member initializer is the moral equivalent of a mem-initializer, these rules should probably be modified not to define the generated constructor as deleted when a union member has a non-static data member initializer. (Note the non-normative references in 11.5 [class.union] paragraphs 2-3 and 9.2.9.2 [dcl.type.cv] paragraph 2 that would also need to be updated if this restriction is changed.)

It would also be helpful to add a requirement to 11.5 [class.union] requiring either a non-static data member initializer or a user-provided constructor if all the members of the union have const-qualified types.

On a more general note, why is the default constructor defined as deleted just because a member has a non-trivial default constructor? The union itself doesn't know which member is the active one, and default construction won't initialize any members (assuming no brace-or-equal-initializer). It is up to the “owner” of the union to control the lifetime of the active member (if any), and requiring a user-provided constructor is forcing a design pattern that doesn't make sense. Along the same lines, why is the default destructor defined as deleted just because a member has a non-trivial destructor? I would agree with this restriction if it only applied when the union also has a user-provided constructor.

See also issues 1460, 1562, 1587, and 1621.




1808. Constructor templates vs default constructors

Section: 11.4.5  [class.ctor]     Status: drafting     Submitter: Richard Smith     Date: 2013-11-12

It is not clear when, if ever, a constructor template can be considered to provide a default constructor. For example:

  struct A {
    template<typename ...T> A(T...); // #1
    A(std::initializer_list<long>);  // #2
  };
  A a{};

According to 9.4.5 [dcl.init.list] paragraph 3, A will be value-initialized if it has a default constructor, and there is implementation divergence whether this example calls #1 or #2.

Similarly, for an example like

  struct B {
    template<typename T=int> B(T = 0);
  };

it is not completely clear whether a default constructor should be implicitly declared or not.

More generally, do utterances in the Standard concerning “constructors” also apply to constructor templates?

Notes from the February, 2014 meeting:

One possibility discussed was that we may need to change places that explicitly refer to a default constructor to use overload resolution, similar to the change that was made a few years ago with regard to copy construction vs “copy constructor.” One additional use of “default constructor” is in determining the triviality of a class, but it might be a good idea to remove the concept of a trivial class altogether. This possibility will be explored.

Notes from the February, 2016 meeting:

CWG reaffirmed the direction from the preceding note and also determined that the presence of a constructor template should suppress implicit declaration of a default constructor.




1092. Cycles in overload resolution during instantiation

Section: 11.4.5.3  [class.copy.ctor]     Status: drafting     Submitter: Jason Merrill     Date: 2010-07-15

Moving to always doing overload resolution for determining exception specifications and implicit deletion creates some unfortunate cycles:

    template<typename T> struct A {
       T t;
    };

    template <typename T> struct B {
       typename T::U u;
    };

    template <typename T> struct C {
       C(const T&);
    };

    template <typename T> struct D {
       C<B<T> > v;
    };

    struct E {
       typedef A<D<E> > U;
    };

    extern A<D<E> > a;
    A<D<E> > a2(a);

If declaring the copy constructor for A<D<E>> is part of instantiating the class, then we need to do overload resolution on D<E>, and thus C<B<E>>. We consider C(const B<E>&), and therefore look to see if there's a conversion from C<B<E>> to B<E>, which instantiates B<E>, which fails because it has a field of type A<D<E>> which is already being instantiated.

Even if we wait until A<D<E>> is considered complete before finalizing the copy constructor declaration, declaring the copy constructor for B<E> will want to look at the copy constructor for A<D<E>>, so we still have the cycle.

I think that to avoid this cycle we need to short-circuit consideration of C(const T&) somehow. But I don't see how we can do that without breaking

    struct F {
       F(F&);
    };

    struct G;
    struct G2 {
       G2(const G&);
    };

    struct G {
       G(G&&);
       G(const G2&);
    };

    struct H: F, G { };

    extern H h;
    H h2(h);

Here, since G's move constructor suppresses the implicit copy constructor, the defaulted H copy constructor calls G(const G2&) instead. If the move constructor did not suppress the implicit copy constructor, I believe the implicit copy constructor would always be viable, and therefore a better match than a constructor taking a reference to another type.

So perhaps the answer is to reconsider that suppression and then disqualify any constructor taking (a reference to) a type other than the constructor's class from consideration when looking up a subobject constructor in an implicitly defined constructor. (Or assignment operator, presumably.)

Another possibility would be that when we're looking for a conversion from C<B<E>> to B<E> we could somehow avoid considering, or even declaring, the B<E> copy constructor. But that seems a bit dodgy.

Additional note (October, 2010):

An explicitly declared move constructor/op= should not suppress the implicitly declared copy constructor/op=; it should cause it to be deleted instead. This should prevent a member function taking a (reference to) an un-reference-related type from being chosen by overload resolution in a defaulted member function.

And we should clarify that member functions taking un-reference-related types are not even considered during overload resolution in a defaulted member function, to avoid requiring their parameter types to be complete.




1548. Copy/move construction and conversion functions

Section: 11.4.5.3  [class.copy.ctor]     Status: drafting     Submitter: Nikolay Ivchenkov     Date: 2012-09-02

The current wording of 11.4.5.3 [class.copy.ctor] paragraph 31 refers only to constructors and destructors:

When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the constructor selected for the copy/move operation and/or the destructor for the object have side effects.

However, in some cases (e.g., auto_ptr) a conversion function is also involved in the copying, and it could presumably also have visible side effects that would be eliminated by copy elision. (Some additional contexts that may also require changes in this regard are mentioned in the resolution of issue 535.)

Additional note (September, 2012):

The default arguments of an elided constructor can also have side effects and should be mentioned, as well; however, the elision should not change the odr-use status of functions and variables appearing in those default arguments.




1594. Lazy declaration of special members vs overload errors

Section: 11.4.5.3  [class.copy.ctor]     Status: drafting     Submitter: Richard Smith     Date: 2012-12-06

The implicit declaration of a special member function sometimes requires overload resolution, in order to select a special member to use for base classes and non-static data members. This can be required to determine whether the member is or would be deleted, and whether the member is trivial, for instance. The standard appears to require such overload resolution be performed at the end of the definition of the class, but in practice, implementations perform it lazily. This optimization appears to be non-conforming, in the case where overload resolution would hit an error. In order to enable this optimization, such errors should be “no diagnostic required.”

Additional note (March, 2013):

See also issue 1360.

Notes from the September, 2013 meeting:

The problem with this approach is that hard errors (not in the immediate context) can occur, affecting portability. There are some cases, such as a virtual assignment operator in the base class, where lazy evaluation cannot be done, so it cannot be mandated.




2203. Defaulted copy/move constructors and UDCs

Section: 11.4.5.3  [class.copy.ctor]     Status: drafting     Submitter: Vinny Romano     Date: 2015-11-20

Consider:

  struct A
  {
    A();
    A(A&);
    explicit A(int);
    operator int() const;
  };
  struct B
  {
    B(B&& other);
    A a;
  };
  B::B(B&& other) : a(static_cast<B&&>(other).a) {}
  // B::B(B&& other) = default; // ill-formed

  void f(B& b1)
  {
    B b2 = static_cast<B&&>(b1);
  }

The user-defined move constructor is well-formed because B::a can be initialized via A::operator int() and A::A(int); however, Clang and GCC believe a defaulted one would be ill-formed.

What about the following, which is considered well-formed by compilers and calls A::A(C&&)?

  struct C {};

  struct A : C
  {
    A();
    A(A&);
    A(C&&);
  };
  struct B
  {
    B(B&& other);
    A a;
  };

  B::B(B&& other) = default;



2264. Memberwise copying with indeterminate value

Section: 11.4.5.3  [class.copy.ctor]     Status: drafting     Submitter: Hubert Tong     Date: 2016-05-06

It appears that the following example may have unwanted undefined behavior in C++, although not in C:

  struct A { int x, y; };
  A passthrough(A a) { return a; }
  int main(void) {
   A a;
   a.x = 0;
   return passthrough(a).x;
  }

The default memberwise copying operation is not specified to be done in a way that is insensitive to indeterminate values.




1499. Missing case for deleted move assignment operator

Section: 11.4.6  [class.copy.assign]     Status: drafting     Submitter: John Spicer     Date: 2012-04-27

Bullet 4 of 11.4.5.3 [class.copy.ctor] paragraph 23 says that a defaulted copy/move assignment operator is defined as deleted if the class has

a non-static data member of class type M (or array thereof) that cannot be copied/moved because overload resolution (12.2 [over.match]), as applied to M's corresponding assignment operator, results in an ambiguity or a function that is deleted or inaccessible from the defaulted assignment operator

The intent of this is that if overload resolution fails to find a corresponding copy/move assignment operator that can validly be called to copy/move a member, the class's assignment operator will be defined as deleted. However, this wording does not cover an example like the following:

  struct A {
    A();
  };

  struct B {
    B();
    const A a;
  };

  typedef B& (B::*pmf)(B&&);

  pmf p =&B::operator=;

Here, the problem is simply that overload resolution failed to find a callable function, which is not one of the cases listed in the current wording. A similar problem exists for base classes in the fifth bullet.

Additional note (January, 2013):

A similar omission exists in paragraph 11 for copy constructors.




2329. Virtual base classes and generated assignment operators

Section: 11.4.6  [class.copy.assign]     Status: drafting     Submitter: Daveed Vandevoorde     Date: 2016-10-31

An example like the following,

  class A {
  private:
    A& operator=(const A&);
  };

  class B : virtual public A {
  public:
    B& operator = (const B& src);
  };

  class C: public B {
  public:
    void f(const C* psrc) {
      *this = *psrc;
    }
  };

is presumably well-formed, even though the copy assignment operator of A is inaccessible in C, because 11.4.6 [class.copy.assign] paragraph 12 says that only direct, not virtual, base class object assignment operators are invoked by the generated assignment operator (although there is implementation divergence on this question).

Should the example also be well-formed if A were a direct virtual base of C? That is, if a direct virtual base also has an indirect derivation path, its direct derivation can be ignored for generated assignment operators.

Possibly relevant to this question is the permission for an implementation to assign virtual base class objects more than once:

It is unspecified whether subobjects representing virtual base classes are assigned more than once by the implicitly-defined copy/move assignment operator.



1977. Contradictory results of failed destructor lookup

Section: 11.4.7  [class.dtor]     Status: drafting     Submitter: Gabriel Dos Reis     Date: 2014-07-21

According to 11.4.7 [class.dtor] paragraph 12,

At the point of definition of a virtual destructor (including an implicit definition (11.4.5.3 [class.copy.ctor])), the non-array deallocation function is looked up in the scope of the destructor's class (6.5.2 [class.member.lookup]), and, if no declaration is found, the function is looked up in the global scope. If the result of this lookup is ambiguous or inaccessible, or if the lookup selects a placement deallocation function or a function with a deleted definition (9.5 [dcl.fct.def]), the program is ill-formed. [Note: This assures that a deallocation function corresponding to the dynamic type of an object is available for the delete-expression (11.4.11 [class.free]). —end note]

However, bullet 5.3 of that section says that such a lookup failure causes the destructor to be defined as deleted, rather than making the program ill-formed. It appears that paragraph 12 was overlooked when deleted functions were added to the language. See also 11.4.11 [class.free] paragraph 7.




2158. Polymorphic behavior during destruction

Section: 11.4.7  [class.dtor]     Status: drafting     Submitter: Richard Smith     Date: 2015-07-13

Consider the following example:

  #include <stdio.h>
  struct Base {
    Base *p;
    virtual void f() { puts("base"); }
    ~Base() {
      p->f();
    }
  };
  struct Derived : Base {
    Derived() { p = this; }
    void f() { puts("derived"); }
    void g() {
      p->f();
      delete this;
    }
  };
  void h() {
    Derived *p = new Derived;
    p->g();
  }

Should this have defined behavior? On the one hand, the Derived object is in its period of destruction, so the behavior of the p->f() call in the Base destructor should be to call Base::f(). On the other hand, p is a pointer to a Derived object whose lifetime has ended, and the rules in 6.7.3 [basic.life] don't appear to allow the call. (Calling this->f() from the Base destructor would be OK — the question is whether you can do that for a pointer that used to point to the derived object, or if you can only do it for a pointer that was “created” after the dynamic type of the object changed to be Base.)

If the above is valid, it has severe implications for devirtualization. The purpose of 6.7.3 [basic.life] paragraph 7 appears to be to allow an implementation to assume that if it will perform two loads of a constant field (for instance, a const member, the implicit pointer for a reference member, or a vptr), and the two loads are performed on the “same pointer value”, then they load the same value.

Should there be a rule for destructors similar to that of 11.4.5 [class.ctor] paragraph 12?

During the construction of a const object, if the value of the object or any of its subobjects is accessed through a glvalue that is not obtained, directly or indirectly, from the constructor's this pointer, the value of the object or subobject thus obtained is unspecified.



1283. Static data members of classes with typedef name for linkage purposes

Section: 11.4.9.3  [class.static.data]     Status: drafting     Submitter: Mike Miller     Date: 2011-03-29

According to 11.4.9.3 [class.static.data] paragraph 4,

Unnamed classes and classes contained directly or indirectly within unnamed classes shall not contain static data members.

There is no such restriction on member functions, and there is no rationale for this difference, given that both static data members and member functions can be defined outside a unnamed class with a typedef name for linkage purposes. (Issue 406 acknowledged the lack of rationale by removing the specious note in 11.4.9.3 [class.static.data] that attempted to explain the restriction but left the normative prohibition in place.)

It would be more consistent to remove the restriction for classes with a typedef name for linkage purposes.

Additional note (August, 2012):

It was observed that, since no definition of a const static data member is required if it is not odr-used, there is no reason to prohibit such members in an unnamed class even without a typedef name for linkage purposes.




1721. Diagnosing ODR violations for static data members

Section: 11.4.9.3  [class.static.data]     Status: drafting     Submitter: Mike Miller     Date: 2013-07-31

Describing the handling of static data members with brace-or-equal-initializers, 11.4.9.3 [class.static.data] paragraph 3 says,

The member shall still be defined in a namespace scope if it is odr-used (6.3 [basic.def.odr]) in the program and the namespace scope definition shall not contain an initializer.

The word “shall” implies a required diagnostic, but this is describing an ODR violation (the static data member might be defined in a different translation unit) and thus should be “no diagnostic required.”




2335. Deduced return types vs member types

Section: 11.4.9.3  [class.static.data]     Status: drafting     Submitter: John Spicer     Date: 2017-01-29

It is not clear how an example like the following should be treated:

  template <class ...> struct partition_indices {
    static auto compute_right () {}
    static constexpr auto right = compute_right;
  };
  auto foo () -> partition_indices<>;
  void f() {
    foo();
  };

The initialization of right is in a context that must be done during the initial parse of the class, but the function body of compute_right is not supposed to be evaluated until the class is complete. Current implementations appear to accept the template case but not the equivalent non-template case. It's not clear why those cases should be treated differently.

If you change the example to include a forward dependency in the body of compute_right, e.g.,

  template <int> struct X {};
  template <class T> struct partition_indices {
    static auto compute_right () { return X<I>(); }
    static constexpr auto right = compute_right;
    static constexpr int I = sizeof(T);
  };

  auto foo () -> partition_indices<int>;

  void f() {
    foo();
  };

current implementations reject the code, but it's not clear that there is a rationale for the different behavior.

Notes from the March, 2018 meeting:

It was proposed that one direction might be to disallow instantiating member functions while the containing class template is being instantiated. However, overnight implementation experience indicated that this approach breaks seemingly-innocuous and currently-accepted code like:

  template <class T> struct A {
    static constexpr int num() { return 42; }
    int ar[num()];
  };
  A<int> a;

There was divergence of opinion regarding whether the current rules describe the current behavior for the two original examples or whether additional explicit rules are needed to clarify the difference in behavior between template and non-template examples, as well as whether there should be a difference at all..

Notes from the June, 2018 meeting:

The consensus of CWG was to treat templates and classes the same by "instantiating" delayed-parse regions when they are needed instead of at the end of the class.

See also issue 1890.




1404. Object reallocation in unions

Section: 11.5  [class.union]     Status: drafting     Submitter: Nikolay Ivchenkov     Date: 2011-10-19

According to 11.5 [class.union] paragraph 4,

[Note: In general, one must use explicit destructor calls and placement new operators to change the active member of a union. —end note] [Example: Consider an object u of a union type U having non-static data members m of type M and n of type N. If M has a non-trivial destructor and N has a non-trivial constructor (for instance, if they declare or inherit virtual functions), the active member of u can be safely switched from m to n using the destructor and placement new operator as follows:

  u.m.~M();
  new (&u.n)  N;

end example]

This pattern is only “safe” if the original object that is being destroyed does not involve any const-qualified or reference types, i.e., satisfies the requirements of 6.7.3 [basic.life] paragraph 7, bullet 3:

Although paragraph 4 of 11.5 [class.union] is a note and an example, it should at least refer to the lifetime issues described in 6.7.3 [basic.life].

Additional note (October, 2013):

See also issue 1776, which suggests possibly changing the restriction in 6.7.3 [basic.life]. If such a change is made, this issue may become moot.




1702. Rephrasing the definition of “anonymous union”

Section: 11.5  [class.union]     Status: drafting     Submitter: Richard Smith     Date: 2013-06-17

11.5 [class.union] paragraph 5 defines an anonymous union as follows:

A union of the form

is called an anonymous union; it defines an unnamed object of unnamed type.

It is obviously intended that a declaration like

    static union { int i; float f; };

is a declaration of that form (cf paragraph 6, which requires the static keyword for anonymous unions declared in namespace scope). However, it would be clearer if the definition were recast in more descriptive terms, e.g.,

An anonymous union is an unnamed class that is defined with the class-key union in a simple-declaration in which the init-declarator-list is omitted. Such a simple-declaration is treated as if it contained a single declarator declaring an unnamed variable of the union's type.

(Note that this definition would require some additional tweaking to apply to class member anonymous union declarations, since simple-declarations are not included as member-declarations.)

As a related point, it is not clear how the following examples are to be treated, and there is implementation variance on some:

   void f() { thread_local union { int a; }; }
   void g() { extern union { int b; }; }
   thread_local union { int c; }; // static is implied by thread_local
   static thread_local union { int d; };
   static const union { int e = 0; }; // is e const? Clang says yes, gcc says no
   static constexpr union { int f = 0; };



2246. Access of indirect virtual base class constructors

Section: 11.8.3  [class.access.base]     Status: drafting     Submitter: Vinny Romano     Date: 2016-03-08

Consider this example from issue 7:

  class Foo { };
  class A : virtual private Foo { };
  class Bar : public A { }; 

This example should cause Bar's defaulted default constructor to be deleted, because it does not have access to the injected-class-name Foo.

Notes from the December, 2016 teleconference:

The injected-class-name is irrelevant to the example, which is ill-formed. The access should be permitted only if conversion of the this pointer to a pointer to the base class would succeed.




2588. friend declarations and module linkage

Section: 11.8.4  [class.friend]     Status: drafting     Submitter: Nathan Sidwell     Date: 2022-05-26     Liaison: EWG

Consider:

  export module Foo;
  class X {
    friend void f(X); // #1 linkage?
  };

Subclause 11.8.4 [class.friend] paragraph 4 gives #1 external linkage:

A function first declared in a friend declaration has the linkage of the namespace of which it is a member (6.6 [basic.link]).

(There is no similar provision for friend classes first declared in a class.)

However, 6.6 [basic.link] bullet 4.8 gives it module linkage:

... otherwise, if the declaration of the name is attached to a named module (10.1 [module.unit]) and is not exported (10.2 [module.interface]), the name has module linkage;

Subclause 10.2 [module.interface] paragraph 2 does not apply:

A declaration is exported if it is declared within an export-declaration and inhabits a namespace scope or it is

Also consider this related example:

  export module Foo;
  export class Y;
  // maybe many lines later, or even a different partition of Foo
  class Y {
    friend void f(Y); // #2 linkage?
  };

See issue 2607 for a similar question about enumerators.

Additional note (May, 2022):

Forwarded to EWG with paper issue 1253, by decision of the CWG chair.

EWG telecon 2022-06-09

Consensus: "A friend's linkage should be affected by the presence/absence of export on the containing class definition itself, but ONLY if the friend is a definition", pending confirmation by electronic polling.

Proposed resolution (June, 2022):

  1. Change in 6.6 [basic.link] paragraph 4 as follows:

    ... The name of an entity that belongs to a namespace scope that has not been given internal linkage above and that is the name of
    • a variable; or
    • a function; or
    • a named class (11.1 [class.pre]), or an unnamed class defined in a typedef declaration in which the class has the typedef name for linkage purposes (9.2.4 [dcl.typedef]); or
    • a named enumeration (9.7.1 [dcl.enum]), or an unnamed enumeration defined in a typedef declaration in which the enumeration has the typedef name for linkage purposes (9.2.4 [dcl.typedef]); or
    • an unnamed enumeration that has an enumerator as a name for linkage purposes (9.7.1 [dcl.enum]); or
    • a template
    has its linkage determined as follows:
    • if the entity is a function or function template first declared in a friend declaration and that declaration is a definition, the name has the same linkage, if any, as the name of the enclosing class (11.8.4 [class.friend]);
    • otherwise, if the entity is a function or function template declared in a friend declaration and a corresponding non-friend declaration is reachable, the name has the linkage determined from that prior declaration,
    • otherwise, if the enclosing namespace has internal linkage, the name has internal linkage;
    • otherwise, if the declaration of the name is attached to a named module (10.1 [module.unit]) and is not exported (10.2 [module.interface]), the name has module linkage;
    • otherwise, the name has external linkage.
  2. Remove 11.8.4 [class.friend] paragraph 4:

    A function first declared in a friend declaration has the linkage of the namespace of which it is a member (6.6 [basic.link]). Otherwise, the function retains its previous linkage (9.2.2 [dcl.stc]).

EWG electronic poll 2022-06

Consensus for "A friend's linkage should be affected by the presence/absence of export on the containing class definition itself, but ONLY if the friend is a definition (option #2, modified by Jason's suggestion). This resolves CWG2588." See vote.




472. Casting across protected inheritance

Section: 11.8.5  [class.protected]     Status: drafting     Submitter: Mike Miller     Date: 16 Jun 2004

Does the restriction in 11.8.5 [class.protected] apply to upcasts across protected inheritance, too? For instance,

    struct B {
        int i;
    };
    struct I: protected B { };
    struct D: I {
        void f(I* ip) {
            B* bp = ip;    // well-formed?
            bp->i = 5;     // aka "ip->i = 5;"
        }
    };

I think the rationale for the 11.8.5 [class.protected] restriction applies equally well here — you don't know whether ip points to a D object or not, so D::f can't be trusted to treat the protected B subobject consistently with the policies of its actual complete object type.

The current treatment of “accessible base class” in 11.8.3 [class.access.base] paragraph 4 clearly makes the conversion from I* to B* well-formed. I think that's wrong and needs to be fixed. The rationale for the accessibility of a base class is whether “an invented public member” of the base would be accessible at the point of reference, although we obscured that a bit in the reformulation; it seems to me that the invented member ought to be considered a non-static member for this purpose and thus subject to 11.8.5 [class.protected].

(See also issues 385 and 471.).

Notes from October 2004 meeting:

The CWG tentatively agreed that casting across protective inheritance should be subject to the additional restriction in 11.8.5 [class.protected].

Proposed resolution (April, 2011)

Change 11.8.3 [class.access.base] paragraph 4 as follows:

A base class B of N is accessible at R, if

[Example:

    class B {
    public:
      int m;
    };

    class S: private B {
      friend class N;
    };
    class N: private S {
      void f() {
        B* p = this;    // OK because class S satisfies the fourth condition
                        // above: B is a base class of N accessible in f() because
                        // B is an accessible base class of S and S is an accessible
                        // base class of N.
      }
    };

    class N2: protected B { };

    class P2: public N2 {
      void f2(N2* n2p) {
        B* bp = n2p;    // error: invented member would be protected and naming
                        // class N2 not the same as or derived from the referencing
                        // class P2
        n2p->m = 0;     // error (cf 11.8.5 [class.protected]) for the same reason
      }
    };

end example]




1883. Protected access to constructors in mem-initializers

Section: 11.8.5  [class.protected]     Status: drafting     Submitter: Daveed Vandevoorde     Date: 2014-02-26

According to 11.8.5 [class.protected] paragraph 1, except when forming a pointer to member,

All other accesses involve a (possibly implicit) object expression (7.6.1.5 [expr.ref]).

It is not clear that this is strictly true for the invocation of a base class constructor from a mem-initializer. A wording tweak may be advisable.




2187. Protected members and access via qualified-id

Section: 11.8.5  [class.protected]     Status: drafting     Submitter: Hubert Tong     Date: 2015-10-16

The following line in the example in 11.8.5 [class.protected] paragraph 1 is no longer allowed following the change from issue 1873:

  class B {
  protected:
    int i;
    static int j;
  };
  // ...
  class D2 : public B {
    friend void fr(B*, D1*, D2*);
    void mem(B*, D1*);
  };
  void fr(B* pb, D1* p1, D2* p2) {
    // ...
    p2->B::i = 4;  // OK (access through a D2, even though naming class is B)
    // ...
  }

The example line ought to work, but none of the bullets in 11.8.3 [class.access.base] paragraph 5 apply:

A member m is accessible at the point R when named in class N if

One aproach might be that 11.8.3 [class.access.base] bullet 5.3 should also consider friends of a class P derived from N where P is the type of the object expression (if any) or a base class thereof, and m as a member of P is public, protected, or private.




2056. Member function calls in partially-initialized class objects

Section: 11.9.3  [class.base.init]     Status: drafting     Submitter: Richard Smith     Date: 2014-12-11

According to 11.9.3 [class.base.init] paragraph 16,

Member functions (including virtual member functions, 11.7.3 [class.virtual]) can be called for an object under construction. Similarly, an object under construction can be the operand of the typeid operator (7.6.1.8 [expr.typeid]) or of a dynamic_cast (7.6.1.7 [expr.dynamic.cast]). However, if these operations are performed in a ctor-initializer (or in a function called directly or indirectly from a ctor-initializer) before all the mem-initializers for base classes have completed, the result of the operation is undefined.

The example in that paragraph reads, in significant part,

  class B {
  public:
    int f();
  };

  class C {
  public:
    C(int);
  };

  class D : public B, C {
  public:
    D() : C(f())  // undefined: calls member function
                  // but base \tcode{C} not yet initialized
    {}
  };

However, the construction of B, the object for which the member function is being called) has completed its construction, so it is not clear why this should be undefined behavior.

(See also issue 1517.)




2403. Temporary materialization and base/member initialization

Section: 11.9.3  [class.base.init]     Status: drafting     Submitter: Daveed Vandevoorde     Date: 2018-12-11

Given the following example,

  struct Noncopyable {
    Noncopyable();
    Noncopyable(const Noncopyable &) = delete;
  };

  Noncopyable make(int kind = 0);

  struct AsBase : Noncopyable {
    AsBase() : Noncopyable(make()) {} // #1
  };

  struct AsMember {
    Noncopyable nc;
    AsMember() : nc(make()) { }  // #2?
  };

All implementations treat #1 as an error, invoking the deleted copy constructor, while #2 is accepted. It's not clear from the current wording why they should be treated differently.

Additional note (August, 2022):

If there are concerns about reuse of tail padding in #1, requiring a copy for some implementation reason, similar concerns should apply to #2 if the data member is declared with [[no_unique_address]].

Furthermore, the following example using a delegating constructor shows implementation divergence:

struct Noncopyable {
  Noncopyable();
  Noncopyable(const Noncopyable &) = delete;
  Noncopyable(int) : Noncopyable(Noncopyable()) {} // #3?
};



1517. Unclear/missing description of behavior during construction/destruction

Section: 11.9.5  [class.cdtor]     Status: drafting     Submitter: Daniel Krügler     Date: 2012-07-07

The current wording of 11.9.5 [class.cdtor] paragraph 4 does not describe the behavior of calling a virtual function in a mem-initializer for a base class, only for a non-static data member. Also, the changes for issue 1202 should have been, but were not, applied to the description of the behavior of typeid and dynamic_cast in paragraphs 5 and 6.

In addition, the resolution of issue 597 allowing the out-of-lifetime conversion of pointers/lvalues to non-virtual base classes, should have been, but were not, applied to paragraph 3.

(See also issue 2056.)

Proposed resolution (August, 2013):

  1. Change 11.9.5 [class.cdtor] paragraph 1 as follows:

  2. For an object with a non-trivial constructor, referring to any non-static member or virtual base class of the object before the constructor begins execution results in undefined behavior. For an object with a non-trivial destructor, referring to any non-static member or virtual base class of the object after the destructor finishes execution results in undefined behavior. [Example:
      struct X { int i; };
      struct Y : X { Y(); };                       // non-trivial
      struct A { int a; };
      struct B : public virtual A { int j; Y y; }; // non-trivial
    
      extern B bobj;
      B* pb = &bobj;                               // OK
      int* p1 = &bobj.a;                           // undefined, refers to base class member
      int* p2 = &bobj.y.i;                         // undefined, refers to member's member
    
      A* pa = &bobj;                               // undefined, upcast to a virtual base class type
      B bobj;                                      // definition of bobj
    
      extern X xobj;
      int* p3 = &xobj.i;                           //OK, X is a trivial class
      X xobj;
    
  3. Change 11.9.5 [class.cdtor] paragraphs 3-6 as follows:

  4. To explicitly or implicitly convert a pointer (a glvalue) referring to an object of class X to a pointer (reference) to a direct or indirect virtual base class B of X, the construction of X and the construction of all of its direct or indirect bases that directly or indirectly derive from for which B is a direct or indirect virtual base shall have started and the destruction of these classes shall not have completed, otherwise the conversion results in undefined behavior. To form a pointer to (or access the value of) a direct non-static member...

    Member functions, including virtual functions (11.7.3 [class.virtual]), can be called during construction or destruction (11.9.3 [class.base.init]). When a virtual function is called directly or indirectly from a constructor or from a destructor, including during the construction or destruction of the class's non-static data members, and the object to which the call applies is the object (call it x) under construction or destruction, the function called is the final overrider in the constructor's or destructor's class and not one overriding it in a more-derived class. If the virtual function call uses an explicit class member access (7.6.1.5 [expr.ref]) and the object expression refers to the complete object of x or one of that object's base class subobjects but not to x or one of its base class subobjects, the behavior is undefined. The period of construction of an object or subobject whose type is a class type C begins immediately after the construction of all its base class subobjects is complete and concludes when the last constructor of class C exits. The period of destruction of an object or subobject whose type is a class type C begins when the destructor for C begins execution and concludes immediately before beginning the destruction of its base class subobjects. A polymorphic operation is a virtual function call (7.6.1.3 [expr.call]), the typeid operator (7.6.1.8 [expr.typeid]) when applied to a glvalue of polymorphic type, or the dynamic_cast operator (7.6.1.7 [expr.dynamic.cast]) when applied to a pointer to or glvalue of a polymorphic type. A polymorphic operand is the object expression in a virtual function call or the operand of a polymorphic typeid or dynamic_cast.

    During the period of construction or period of destruction of an object or subobject whose type is a class type C (call it x), the effect of performing a polymorphic operation in which the polymorphic operand designates x or a base class subobject thereof is as if the dynamic type of the object were class C. [Footnote: This is true even if C is an abstract class, which cannot be the type of a most-derived object. —end footnote] If a polymorphic operand refers to an object or subobject having class type C before its period of construction begins or after its period of destruction is complete, the behavior is undefined. [Note: This includes the evaluation of an expression appearing in a mem-initializer of C in which the mem-initializer-id designates C or one of its base classes. —end note] [Example:

      struct V {
        V();
        V(int);
        virtual void f();
        virtual void g();
      };
    
      struct A : virtual V {
        virtual void f();
        virtual int h();
        A() : V(h()) { }     // undefined behavior: virtual function h called
                             // before A's period of construction begins
      };
    
      struct B : virtual V {
        virtual void g();
        B(V*, A*);
      };
    
      struct D : A, B {
        virtual void f();
        virtual void g();
        D() : B((A*)this, this) { }
      };
    
      B::B(V* v, A* a) {
        f();                 // calls V::f, not A::f
        g();                 // calls B::g, not D::g
        v->g();              // v is base of B, the call is well-defined, calls B::g
        a->f();              // undefined behavior, a's type not a base of B
        typeid(*this);       // type_info for B
        typeid(*v);          // well-defined: *v has type V, a base of B,
                             // so its period of construction is complete;
                             // yields type_info for B
        typeid(*a);          // undefined behavior: A is not a base of B,
                             // so its period of construction has not begun
        dynamic_cast<B*>(v); // well-defined: v has type V*, V is a base of B,
                             // so its period of construction is complete;
                             // results in this
        dynamic_cast<B*>(a); // undefined behavior: A is not a base of B,
                             // so its period of construction has not begun
      }
    

    end example]

    The typeid operator (7.6.1.8 [expr.typeid]) can be used during construction or destruction (11.9.3 [class.base.init]). When typeid is used in a constructor (including the mem-initializer or brace-or-equal-initializer for a non-static data member) or in a destructor, or used in a function called (directly or indirectly) from a constructor or destructor, if the operand of typeid refers to the object under construction or destruction, typeid yields the std::type_info object representing the constructor or destructor's class. If the operand of typeid refers to the object under construction or destruction and the static type of the operand is neither the constructor or destructor's class nor one of its bases, the result of typeid is undefined.

    dynamic_casts (7.6.1.7 [expr.dynamic.cast]) can be used during construction or destruction (11.9.3 [class.base.init]). When a dynamic_cast is used in a constructor (including the mem-initializer or brace-or-equal-initializer for a non-static data member) or in a destructor, or used in a function called (directly or indirectly) from a constructor or destructor, if the operand of the dynamic_cast refers to the object under construction or destruction, this object is considered to be a most derived object that has the type of the constructor or destructor's class. If the operand of the dynamic_cast refers to the object under construction or destruction and the static type of the operand is not a pointer to or object of the constructor or destructor's own class or one of its bases, the dynamic_cast results in undefined behavior. [Example:

      struct V {
        virtual void f();
      };
    
      struct A : virtual V { };
    
      struct B : virtual V {
        B(V*, A*);
      };
    
      struct D : A, B {
        D() : B((A*)this, this) { }
      };
    
      B::B(V* v, A* a) {
        typeid(*this);       // type_info for B
        typeid(*v);          // well-defined: *v has type V, a base of B
                             // yields type_info for B
        typeid(*a);          // undefined behavior: type A not a base of B
        dynamic_cast<B*>(v); // well-defined: v of type V*, V base of B
                             // results in B*
        dynamic_cast<B*>(a); // undefined behavior,
                             // a has type A*, A not a base of B
    

    end example]




1278. Incorrect treatment of contrived object

Section: 12.2.2.2.2  [over.call.func]     Status: drafting     Submitter: Nikolay Ivchenkov     Date: 2011-03-27

Footnote 127 of 12.2.2.2.2 [over.call.func] paragraph 3 reads,

An implied object argument must be contrived to correspond to the implicit object parameter attributed to member functions during overload resolution. It is not used in the call to the selected function. Since the member functions all have the same implicit object parameter, the contrived object will not be the cause to select or reject a function.

It is not true that “the member functions all have the same implicit object parameter.” This statement does not take into account member functions brought into the class by using-declarations or cv-qualifiers and ref-qualifiers on the non-static member functions:

    struct B
    {
      char f();         // B &
    };

    struct D : B
    {
      using B::f;
      long f();         // D &

      char g() const;   // D const &
      long g();         // D &

      char h() &;       // D &
      long h() &&;      // D &&
    };

    int main()
    {
      // D::f() has better match than B::f()
      decltype(D().f()) *p1 = (long *)0;

      // D::g() has better match than D::g() const
      decltype(D().g()) *p2 = (long *)0;

      // D::h() & is not viable function
      // D::h() && is viable function
      decltype(D().h()) *p3 = (long *)0;
    }

The value category of a contrived object expression is not specified by the rules and, probably, cannot be properly specified in presence of ref-qualifiers, so the statement “the contrived object will not be the cause to select or reject a function” should be normative rather than informative:

    struct X
    {
      static void f(double) {}
      void f(int) & {}
      void f(int) && {}
    };

    int main()
    {
      X::f(0); // ???
    }



2089. Restricting selection of builtin overloaded operators

Section: 12.2.2.3  [over.match.oper]     Status: drafting     Submitter: Hubert Tong     Date: 2015-02-26

The candidates selected by 12.2.2.3 [over.match.oper] include built-in candidates that will result in an error if chosen; this was affirmed by issue 1687. As a result, t+u is ill-formed because it is resolved to the built-in operator+(int*,std::ptrdiff_t), although most implementations do not (yet) agree:

  struct Adaptor { Adaptor(int); };

  struct List { };
  void operator +(List &, Adaptor);

  struct DataType {
    operator int *() const = delete;
    operator List &() const;
  };

  struct Yea;
  struct Nay { int theNaysHaveIt; };

  template <typename T, typename U>
  Yea addCheck(int, T &&t, U &&u, char (*)[sizeof(t + u, 0)] = 0);

  template <typename T, typename U>
  Nay addCheck(void *, T &&t, U &&u);

  void test(DataType &data) { (void)sizeof(addCheck(0, data,
  0.).theNaysHaveIt); }

It might be better to adjust the candidate list in 12.2.2.4 [over.match.ctor] bullet 3.3.3 to allow conversion only on class types and exclude the second standard conversion sequence.




2028. Converting constructors in rvalue reference initialization

Section: 12.2.2.7  [over.match.ref]     Status: drafting     Submitter: Mitsuru Kariya     Date: 2014-10-25

Consider the following example:

  struct T {
    T() {}
    T(struct S&) {}
  };

  struct S {
    operator T() { return T(); }
  };

  int main()
  {
    S s;
    T&& t(s);  // #1
  }

Because there are two possible conversions from S to T, one by conversion function and the other by converting constructor, one might expect that the initialization at #1 would be ambiguous. However, 12.2.2.7 [over.match.ref] (used in the relevant bullet of 9.4.4 [dcl.init.ref], paragraph 5.2.1.2) only deals with conversion functions and ignores converting constructors.

Notes from the November, 2014 meeting:

CWG agreed that 9.4.4 [dcl.init.ref] should be changed to consider converting constructors in this case.




2108. Conversions to non-class prvalues in reference initialization

Section: 12.2.2.7  [over.match.ref]     Status: drafting     Submitter: Hubert Tong     Date: 2015-03-24

In 12.2.2.7 [over.match.ref], candidates that produce non-class prvalues are considered, although that seems to contradict what 9.4.4 [dcl.init.ref] says. See also issue 2077.




2194. Impossible case in list initialization

Section: 12.2.2.8  [over.match.list]     Status: drafting     Submitter: Robert Haberlach     Date: 2015-11-04

According to 12.2.2.8 [over.match.list] paragraph 1 says,

If the initializer list has no elements and T has a default constructor, the first phase is omitted.

However, this case cannot occur. If T is a non-aggregate class type with a default constructor and the initializer is an empty initializer list, the object will be value-constructed, per 9.4.5 [dcl.init.list] bullet 3.4. Overload resolution is only necessary if default-initialization (or a check of its semantic constraints) is implied, with the relevant section concerning candidates for overload resolution being 12.2.2.4 [over.match.ctor].

See also issue 1518.

Proposed resolution (January, 2017):

Change 12.2.2.8 [over.match.list] paragraph 1 as follows:

When objects of non-aggregate class type T are list-initialized such that 9.4.5 [dcl.init.list] specifies that overload resolution is performed according to the rules in this section, overload resolution selects the constructor in two phases:

If the initializer list has no elements and T has a default constructor, the first phase is omitted. In copy-list-initialization, if an explicit constructor is chosen...

Additional notes, February, 2017:

The statement of the issue is incorrect. In an example like

  struct A { A(); A(initializer_list<int>); };
  void f(A a);
  int main() { f({}); }

the rule in question is not used for the initialization of the parameter. However, it is used to determine whether a valid implicit conversion sequence exists for a. It is unclear whether an additional change to resolve this discrepancy is needed or not.




2467. CTAD for alias templates and the deducible check

Section: 12.2.2.9  [over.match.class.deduct]     Status: drafting     Submitter: Richard Smith     Date: 2019-08-12

Given the declarations

  template<typename T = int> using X = vector<int>;
  X x = {1, 2, 3};

  template<typename...> using Y = vector<int>;
  Y y = {1, 2, 3};

CTAD deduces vector<int>. Then we are asked to perform a check that the arguments of X and Y are deducible from vector<int>.

I think this check should succeed, deducing T = int in the first case and <pack> = <empty> in the second case, so both declarations should be valid. That seems consistent with what would happen for a non-alias with template parameters that CTAD can't deduce, where there is either a default template argument or the parameter is a pack. But what actually happens is that we're asked to form

  template<typename T> struct AA;
  template<typename T = int> struct AA<X<T>>;

and

  template<typename T> struct AA;
  template<typename ...Ts> struct AA<Y<Ts...>>;

However, both of those partial specializations are ill-formed: a partial specialization can't have default template arguments, and neither of these is more specialized than the primary template, because T / Ts are not used in deducible contexts.

I think we have the wrong model here, and should instead be considering (effectively) whether function template argument deduction would succeed for

  template<typename T> struct AA {};
  template<typename T = int> void f(AA<X<T>>);

and

  template<typename T> struct AA {};
  template<typename ...Ts> void f(AA<Y<Ts...>>);

respectively, when given an argument of type AA<deduced return type>. That is, get rid of the weird class template partial specialization restrictions, and instead add in the rules from function templates to use default template arguments and to default non-deduced packs to empty packs.




2471. Nested class template argument deduction

Section: 12.2.2.9  [over.match.class.deduct]     Status: drafting     Submitter: John Spicer     Date: 2021-01-26

Consider the following example:

  template<class T> struct S {
    template<class U> struct N {
      N(T) {}
      N(T, U) {}
      template<class V> N(V, U) {}
    };
  };
  S<int>::N x{2.0, 1};

The description of CTAD in 12.2.2.9 [over.match.class.deduct] doesn't really specify how nested classes work. If you are supposed to deduce all the enclosing class template arguments, the example is ill-formed because there is no way to deduce T. If you are supposed to consider S<int>::N as having a new constructor template, then it should probably be well-formed.

Notes from the March, 2021 teleconference:

CWG agreed that the intent is to use the partially-instantiated inner template with the explicitly-specified template argument int.




2319. Nested brace initialization from same type

Section: 12.2.4.2  [over.best.ics]     Status: drafting     Submitter: Richard Smith     Date: 2016-09-06

Consider:

  struct A { A(); } a;
  A a1 = {a}, a2 = {{a}}, a3 = {{{a}}};

a1 and a2 are valid, a3 is ill-formed, because 12.2.4.2 [over.best.ics] bullet 4.5 allows one pair of braces and 12.2.4.2.6 [over.ics.list] paragraph 2 allows a second pair of braces. The implicit conversion sequence from {{a}} to A is a user-defined conversion.

Prior to the list-initialization-from-same-type changes via issues 1467 and 2076, a2 was ill-formed like a3.

Is this intended, or did DR2076 not go far enough in reintroducing the restriction? Perhaps a more extreme rule, such as saying that a copy/move constructor is simply not a candidate for list-initialization from a list that contains one element that is itself a list, would work better?

Notes from the July, 2017 meeting:

CWG agreed that the a2 example should be ill-formed but that the a1 example must remain for C compatibility.




2525. Incorrect definition of implicit conversion sequence

Section: 12.2.4.2.1  [over.best.ics.general]     Status: drafting     Submitter: Jim X     Date: 2021-09-25

According to 12.2.4.2.1 [over.best.ics.general] paragraphs 1 and 9,

An implicit conversion sequence is a sequence of conversions used to convert an argument in a function call to the type of the corresponding parameter of the function being called. The sequence of conversions is an implicit conversion as defined in 7.3 [conv], which means it is governed by the rules for initialization of an object or reference by a single expression (9.4 [dcl.init], 9.4.4 [dcl.init.ref]).

If no sequence of conversions can be found to convert an argument to a parameter type, an implicit conversion sequence cannot be formed.

However, 7.3.1 [conv.general] paragraph 3 says,

An expression E can be implicitly converted to a type T if and only if the declaration T t=E; is well-formed, for some invented temporary variable t (9.4 [dcl.init]).

This definition is too restrictive in the context of overload resolution's implicit conversion sequences. The intent, as stated in 12.2.1 [over.match.general] note 1, is that overload resolution ignores some factors that would make such an initialization ill-formed, and these are applied only after the best match is determined:

[Note 1: The function selected by overload resolution is not guaranteed to be appropriate for the context. Other restrictions, such as the accessibility of the function, can make its use in the calling context ill-formed. —end note]

For example,

  struct A{
    A(int) = delete;
  };
  struct B{
     B(int) {}
  };
  void fun(A); // #1
  void fun(B); // #2
  int main() {
    fun(0);    // #3
  }

The intent is that overload #1 be viable with a valid implicit conversion sequence, making the call at #3 ambiguous, even though the hypothetical declaration

  A t = 1;

would be ill-formed.

Proposed resolution (approved by CWG 2022-12-02):

Change 12.2.4.2.1 [over.best.ics.general] paragraph 1, merging it with paragraph 2,as follows:

An implicit conversion sequence is a sequence of conversions used to convert an argument in a function call to the type of the corresponding parameter of the function being called. The sequence of conversions is an implicit conversion as defined in 7.3 [conv], which means; it is governed by thus based on the rules for initialization of an object or reference by a single expression (9.4 [dcl.init], 9.4.4 [dcl.init.ref]). Implicit, except that implicit conversion sequences are concerned only with the type, cv-qualification, and value category of the argument and how these are converted to match the corresponding properties of the parameter. [Note: ... ]

CWG 2023-02-06

Additional drafting is needed to cover e.g. conversions from literal 0 to null pointer constants.




2077. Overload resolution and invalid rvalue-reference initialization

Section: 12.2.4.2.5  [over.ics.ref]     Status: drafting     Submitter: Richard Smith     Date: 2015-01-29

The resolution of issue 1604 broke the following example:

  struct A {};
  struct B { operator const A() const; };
  void f(A const&);
  void f(A&&);

  int main() {
    B a;
    f(a);
  }

Overload resolution selects the A&& overload, but then initialization fails. This seems like a major regression; we're now required to reject

   std::vector<A> va;
   B b;
   va.push_back(b);

Should we update 12.2.4.2.5 [over.ics.ref] to match the changes made to 9.4.4 [dcl.init.ref]?

See also issue 2108.




1536. Overload resolution with temporary from initializer list

Section: 12.2.4.2.6  [over.ics.list]     Status: drafting     Submitter: Mike Miller     Date: 2012-08-14

In determining the implicit conversion sequence for an initializer list argument passed to a reference parameter, the intent is that a temporary of the appropriate type will be created and bound to the reference, as reflected in 12.2.4.2.6 [over.ics.list] paragraph 5:

Otherwise, if the parameter is a reference, see 12.2.4.2.5 [over.ics.ref]. [Note: The rules in this section will apply for initializing the underlying temporary for the reference. —end note]

However, 12.2.4.2.5 [over.ics.ref] deals only with expression arguments, not initializer lists:

When a parameter of reference type binds directly (9.4.4 [dcl.init.ref]) to an argument expression, the implicit conversion sequence is the identity conversion, unless the argument expression has a type that is a derived class of the parameter type, in which case the implicit conversion sequence is a derived-to-base Conversion (12.2.4.2 [over.best.ics])... If the parameter binds directly to the result of applying a conversion function to the argument expression, the implicit conversion sequence is a user-defined conversion sequence (12.2.4.2.3 [over.ics.user]), with the second standard conversion sequence either an identity conversion or, if the conversion function returns an entity of a type that is a derived class of the parameter type, a derived-to-base Conversion.

When a parameter of reference type is not bound directly to an argument expression, the conversion sequence is the one required to convert the argument expression to the underlying type of the reference according to 12.2.4.2 [over.best.ics]. Conceptually, this conversion sequence corresponds to copy-initializing a temporary of the underlying type with the argument expression. Any difference in top-level cv-qualification is subsumed by the initialization itself and does not constitute a conversion.

(Note in particular that the reference binding refers to 9.4.4 [dcl.init.ref], which also does not handle initializer lists, and not to 9.4.5 [dcl.init.list].)

Either 12.2.4.2.5 [over.ics.ref] needs to be revised to handle binding references to initializer list arguments or 12.2.4.2.6 [over.ics.list] paragraph 5 needs to be clearer on how the expression specification is intended to be applied to initializer lists.




2110. Overload resolution for base class conversion and reference/non-reference

Section: 12.2.4.3  [over.ics.rank]     Status: drafting     Submitter: Alexander Kulpin     Date: 2015-03-27

There are overload tiebreakers that order reference/nonreference and base/derived conversions, but how they relate is not specified. For example:

  struct A { A(); };
  struct B : A {};
  struct C : B {};

  void f1(B&);
  void f1(A);

  void f2(B);
  void f2(A&);

  int main()
  {
     C v;
     f1(v); // all compilers choose f1(B&)
     f2(v); // all compilers choose f2(B)
  }

The Standard does not appear to specify what happens in this case.




1989. Insufficient restrictions on parameters of postfix operators

Section: 12.4  [over.oper]     Status: drafting     Submitter: Richard Smith     Date: 2014-08-30

According to 12.4.7 [over.inc] paragraph 1,

The user-defined function called operator++ implements the prefix and postfix ++ operator. If this function is a non-static member function with no parameters, or a non-member function with one parameter, it defines the prefix increment operator ++ for objects of that type. If the function is a non-static member function with one parameter (which shall be of type int) or a non-member function with two parameters (the second of which shall be of type int), it defines the postfix increment operator ++ for objects of that type.

According to 12.4 [over.oper] paragraph 8,

Operator functions cannot have more or fewer parameters than the number required for the corresponding operator, as described in the rest of this subclause.

This does not rule out an operator++ with more than two parameters, however, since there is no corresponding operator.

One possibility might be to add a sentence like,

A function named operator++ shall declare either a prefix or postfix increment operator.



205. Templates and static data members

Section: Clause 13  [temp]     Status: drafting     Submitter: Mike Miller     Date: 11 Feb 2000

Static data members of template classes and of nested classes of template classes are not themselves templates but receive much the same treatment as template. For instance, Clause 13 [temp] paragraph 1 says that templates are only "classes or functions" but implies that "a static data member of a class template or of a class nested within a class template" is defined using the template-declaration syntax.

There are many places in the clause, however, where static data members of one sort or another are overlooked. For instance, Clause 13 [temp] paragraph 6 allows static data members of class templates to be declared with the export keyword. I would expect that static data members of (non-template) classes nested within class templates could also be exported, but they are not mentioned here.

Paragraph 8, however, overlooks static data members altogether and deals only with "templates" in defining the effect of the export keyword; there is no description of the semantics of defining a static data member of a template to be exported.

These are just two instances of a systematic problem. The entire clause needs to be examined to determine which statements about "templates" apply to static data members, and which statements about "static data members of class templates" also apply to static data members of non-template classes nested within class templates.

(The question also applies to member functions of template classes; see issue 217, where the phrase "non-template function" in 9.3.4.7 [dcl.fct.default] paragraph 4 is apparently intended not to include non-template member functions of template classes. See also issue 108, which would benefit from understanding nested classes of class templates as templates. Also, see issue 249, in which the usage of the phrase "member function template" is questioned.)

Notes from the 4/02 meeting:

Daveed Vandevoorde will propose appropriate terminology.




1463. extern "C" alias templates

Section: 13.1  [temp.pre]     Status: drafting     Submitter: Daveed Vandevoorde     Date: 2011-08-19     Liaison: EWG

Currently 13.1 [temp.pre] paragraph 6 forbids any template from having C linkage. Should alias templates be exempt from this prohibition, since they do not have any linkage?

Additional note, April, 2013:

It was suggested that relaxing this restriction for alias templates could provide a way of addressing the long-standing lack of a way of specifying a language linkage for a dependent function type (see issue 13).

Rationale (April, 2013):

CWG felt that this suggested use of alias templates should be considered in a broader context and thus was more appropriate for EWG.

EWG 2022-11-11

extern "C" on a template should be allowed, and should affect only calling convention, but not mangling. This is tracked in github issue cplusplus/papers#1373.




1444. Type adjustments of non-type template parameters

Section: 13.2  [temp.param]     Status: drafting     Submitter: Johannes Schaub     Date: 2012-01-15

The type adjustment of template non-type parameters described in 13.2 [temp.param] paragraph 8 appears to be underspecified. For example, implementations vary in their treatment of

  template<typename T, T[T::size]> struct A {};
  int dummy;
  A<int, &dummy> a;

and

  template<typename T, T[1]> struct A;
  template<typename T, T*> struct A {};
  int dummy;
  A<int, &dummy> a;

See also issues 1322 and 1668.

Additional note, February, 2021:

See the discussion regarding top-level cv-qualifiers on template parameters when determining the type in this compiler bug report.




1635. How similar are template default arguments to function default arguments?

Section: 13.2  [temp.param]     Status: drafting     Submitter: Richard Smith     Date: 2013-03-06

Default function arguments are instantiated only when needed. Is the same true of default template arguments? For example, is the following well-formed?

  #include <type_traits>

  template<class T>
  struct X {
    template<class U = typename T::type>
    static void foo(int){}
    static void foo(...){}
  };

  int main(){
    X<std::enable_if<false>>::foo(0);
  }

Also, is the effect on lookup the same? E.g.,

  struct S {
    template<typename T = U> void f();
    struct U {};
  };

Additional note (November, 2020):

Paper P1787R6, adopted at the November, 2020 meeting, partially addresses this issue.




2395. Parameters following a pack expansion

Section: 13.2  [temp.param]     Status: drafting     Submitter: Richard Smith     Date: 2018-12-03

The Standard is not clear, and there is implementation divergence, for an example like the following:

  template<class ...Types> struct Tuple_ { // _VARIADIC_TEMPLATE 
    template<Types ...T, int I> int f() {
      return sizeof...(Types);
    }
  };
  int main() {
    Tuple_<char,int> a;
    int b = a.f<1, 2, 3>();
  }

The question is whether the 3 is accepted as the argument for I or an error, exceeding the number of arguments for T, which is set as 2 by the template arguments for Tuple_. See also issue 2383 for a related example.




2450. braced-init-list as a template-argument

Section: 13.3  [temp.names]     Status: drafting     Submitter: Marek Polacek     Date: 2019-01-07

Since non-type template parameters can now have class types, it would seem to make sense to allow a braced-init-list as a template-argument, but the grammar does not permit it.

See also issues 2049 and 2459.




2043. Generalized template arguments and array-to-pointer decay

Section: 13.4.3  [temp.arg.nontype]     Status: drafting     Submitter: Richard Smith     Date: 2014-11-13

According to 13.4.3 [temp.arg.nontype] paragraph 1 (newly revised by the adoption of paper N4268),

For a non-type template-parameter of reference or pointer type, the value of the constant expression shall not refer to (or for a pointer type, shall not be the address of):

This change breaks an example like

   template<int *p> struct X {};
   int arr[32];
   X<arr> x;

because the array-to-pointer decay produces a pointer to the first element, which is a subobject.

Suggested resolution:

Change the referenced bullet to read:

Note that this resolution also allows an example like

    template<char &p> struct S { };
    char arr[2];
    S<arr[0]> s_arr;

which may not be exactly what we want.

See also issue 2401.




2049. List initializer in non-type template default argument

Section: 13.4.3  [temp.arg.nontype]     Status: drafting     Submitter: Ville Voutilainen     Date: 2014-11-20

According to 13.4.3 [temp.arg.nontype] paragraph 1,

A template-argument for a non-type template-parameter shall be a converted constant expression (7.7 [expr.const]) of the type of the template-parameter.

This does not permit an example like:

  template <int* x = {}> struct X {};

which seems inconsistent.

See also issues 2450 and 2459.




2401. Array decay vs prohibition of subobject non-type arguments

Section: 13.4.3  [temp.arg.nontype]     Status: drafting     Submitter: John Spicer     Date: 2019-02-06

Consider an example like:

  template <const char *N> struct A { static const int val; };

  template <const char *N> const int A<N>::val = 0;

  static const char c[2] = "";

  int main() {
    A<c> a;
    return A<c>::val;
  }

Formally, this appears to violate the prohibition of using the address of a subobject as a non-type template argument, since the array reference c in the argument decays to a pointer to the first element of the array. However, at least some implementations accept this example, and at least conceptually the template argument designates the complete object. Should an exception be made for the result of array decay?

See also issue 2043.

Notes from the July, 2019 meeting

CWG felt that the example should be allowed if the parameter type is a pointer to object type (thus prohibiting void*).




2459. Template parameter initialization

Section: 13.4.3  [temp.arg.nontype]     Status: drafting     Submitter: Davis Herring     Date: 2020-09-21

The initialization of template parameters is severely underspecified. The only descriptions in the existing wording that apply are that the argument is “[converted] to the type of the template-parameter” (13.6 [temp.type] bullet 1.3) and, in 13.4.3 [temp.arg.nontype] paragraph 2,

A template-argument for a non-type template-parameter shall be a converted constant expression (7.7 [expr.const]) of the type of the template-parameter.

This omission is particularly important for template parameters of class type with lvalue template parameter objects whose addresses can be examined during construction. See also issue 2450.

Suggested resolution:

To avoid address-based paradoxes, template arguments for a template parameter of class type C that are not of that type or a derived type are converted to C to produce an exemplar. No restrictions are imposed on the conversion from a template argument to a constructor parameter, since explicit and list-initialization may already be used to limit conversions in a similar fashion. Template arguments that are of such a type are used directly as the exemplar (potentially after a materialization conversion); the effect is as if the template parameter were of type const C& (except that temporaries are allowed). (In the latter case, we must impose some restrictions on glvalue template parameters to interpret them.) Each exemplar is used to copy-initialize the template parameter object to which it is (to be) template-argument-equivalent; the initialization is required to produce a template-argument-equivalent value. The multiple initializations of the template parameter object are (required to be) all equivalent and produce no side effects, so it is unobservable which happen.




2057. Template template arguments with default arguments

Section: 13.4.4  [temp.arg.template]     Status: drafting     Submitter: Jonathan Caves     Date: 2014-12-12

It is not clear how to handle an example like:

  template<typename T1, typename T2 = char> class A { };

  template<template<typename... T> class X> class S {
    X<int> x;
  };

  S<A> a;

Issue 184 dealt with a similar question but did so in the era before variadic templates. This usage should be permitted in modern C++.

Notes from the February, 2016 meeting:

CWG felt that this usage should be permitted, but only for template template parameters with a parameter pack.. Furthermore, if the template template parameter has a default argument followed by a parameter pack, the parameter's default argument would be used, followed by any remaining default arguments from the template template argument.




2398. Template template parameter matching and deduction

Section: 13.4.4  [temp.arg.template]     Status: drafting     Submitter: Jason Merrill     Date: 2016-12-03

Do the changes from P0522R0 regarding template template parameter matching apply to deduction? For example:

  template<class T, class U = T> class B { /* ... */ };
  template<template<class> class P, class T> void f(P<T>);

  int main()  {
    f(B<int>());       // OK?
    f(B<int,float>()); // ill-formed, T deduced to int and float
  }

In deduction we can determine that P is more specialized than B, then substitute B into P<T>, and then compare B<T,T> to B<int,int>. This will allow deduction to succeed, whereas comparing <T> to <int,int> without this substitution would fail. I suppose this is similar to deducing a type parameter, substituting it into the type of a non-type parameter, then deducing the value of the non-type parameter

Does this make sense? Do we need more wording?

Consider also this example;

  template<typename> struct match;

  template<template<typename> class t,typename T>
  struct match<t<T> > { typedef int type; };      // #1

  template<template<typename,typename> class t,typename T0,typename T1>
  struct match<t<T0,T1> > { typedef int type; };  // #2

  template<typename,typename = void> struct other { };
  typedef match<other<void,void> >::type type;

Before this change, partial specialization #1 was not a candidate; now it is, and neither partial specialization is at least as specialized as the other, so we get an ambiguity. It seems that the consistent way to address this would be to use other during partial ordering, so we'd be comparing

  template<typename T>
  void fn (match<other<T>>); // i.e. other<T,void>
  template<typename T0, typename T1>
  void fn (match<other<T0,T1>>);

So #1 is more specialized, whereas before this change we chose #2.




2037. Alias templates and template declaration matching

Section: 13.6  [temp.type]     Status: drafting     Submitter: Richard Smith     Date: 2014-11-06

For the following example,

  template<int N> struct A {};
  template<short N> using B = A<N>;
  template<int N> void f(B<N>) {} // #1
  template<int N> void f(A<N>) {} // #2

There is implementation variance as to whether there is one f or two. As with previously-discussed cases, these have different SFINAE effects, perhaps equivalent but not functionally equivalent. Should the argument to #1 be treated as something like A<(int)(short)N> and not just A<N>.

See also issues 1668 and 1979.




1730. Can a variable template have an unnamed type?

Section: 13.7  [temp.decls]     Status: drafting     Submitter: Larisse Voufo     Date: 2013-08-05

Is it permitted for a variable template to have an unnamed type?




1647. Type agreement of non-type template arguments in partial specializations

Section: 13.7.6  [temp.spec.partial]     Status: drafting     Submitter: John Spicer     Date: 2013-04-04

The Standard appears to be silent on whether the types of non-type template arguments in a partial specialization must be the same as those of the primary template or whether conversions are permitted. For example,

  template<char...> struct char_values {};
  template<int C1, char C3>
  struct char_values<C1, 12, C3> {
    static const unsigned value = 1;
  };
  int check0[char_values<1, 12, 3>::value == 1? 1 : -1];

The closest the current wording comes to dealing with this question is 13.7.6.1 [temp.spec.partial.general] bullet 9.1:

In this example, one might think of the first template argument in the partial specialization as (char)C1, which would violate the requirement, but that reasoning is tenuous.

It would be reasonable to require the types to match in cases like this. If this kind of usage is allowed it could get messy if the primary template were int... and the partial specialization had a parameter that was char because not all of the possible values from the primary template could be represented in the parameter of the partial specialization. A similar issue exists if the primary template takes signed char and the partial specialization takes unsigned int.

There is implementation variance in the treatment of this example.

(See also issues 1315, 2033, and 2127.)




2127. Partial specialization and nullptr

Section: 13.7.6  [temp.spec.partial]     Status: drafting     Submitter: Faisal Vali     Date: 2015-05-18

An example like the following would seem to be plausible:

  template<class T, T*> struct X { };
  // We want to partially specialize for all nullptrs...
  template<class T> struct X<T, nullptr> { ... }; // NOT OK

This is disallowed by the rule in bullet 9.2 of 13.7.6.1 [temp.spec.partial.general]:

(See also issues 1315, 1647, and 2033.)




2179. Required diagnostic for partial specialization after first use

Section: 13.7.6.1  [temp.spec.partial.general]     Status: drafting     Submitter: John Spicer     Date: 2015-10-12

According to 13.7.6.1 [temp.spec.partial.general] paragraph 1,

A partial specialization shall be declared before the first use of a class template specialization that would make use of the partial specialization as the result of an implicit or explicit instantiation in every translation unit in which such a use occurs; no diagnostic is required.

There are two problems with this wording. First, the “no diagnostic required” provision is presumably to avoid mandating cross-translation-unit analysis, but there is no reason not to require the diagnostic if the rule is violated within a single translation unit. Also, “would make use” is imprecise; it could be interpreted as applying only when the partial specialization would have been selected by a previous specialization, but it should also apply to cases where the partial specialization would have made a previous specialization ambiguous.

Making these two changes would guarantee that a diagnostic is issued for the following example:

   template <class T1, class T2> class A;
   template <class T> struct A<T, void> { void f(); };
   template <class T> void g(T) { A<char, void>().f(); }   // #1
   template<typename T> struct A<char, T> {};
   A<char, void> f;   // #2

It is unspecified whether the reference to A<char, void> at #1 is the “first use” or not. If so, A<char, void> is bound to the first partial specialization and, under the current wording, an implementation is not required to diagnose the ambiguity resulting from the second partial specialization. If #2 is the “first use,” it is clearly ambiguous and must result in a diagnostic. There is implementation divergence on the handling of this example that would be addressed by the suggested changes.




549. Non-deducible parameters in partial specializations

Section: 13.7.6.2  [temp.spec.partial.match]     Status: drafting     Submitter: Martin Sebor     Date: 18 November 2005

In the following example, the template parameter in the partial specialization is non-deducible:

    template <class T> struct A { typedef T U; };
    template <class T> struct C { };
    template <class T> struct C<typename A<T>::U> { };

Several compilers issue errors for this case, but there appears to be nothing in the Standard that would make this ill-formed; it simply seems that the partial specialization will never be matched, so the primary template will be used for all specializations. Should it be ill-formed?

(See also issue 1246.)

Notes from the April, 2006 meeting:

It was noted that there are similar issues for constructors and conversion operators with non-deducible parameters, and that they should probably be dealt with similarly.

Additional note, December, 2021:

The original issue, but not the *#8220;similar issues *#8221; pointed out in the 2006-04 note, was resolved by the changes for issue 1315 and paper P0127R2.




1755. Out-of-class partial specializations of member templates

Section: 13.7.6.4  [temp.spec.partial.member]     Status: drafting     Submitter: Richard Smith     Date: 2013-09-19

According to 13.7.6.4 [temp.spec.partial.member] paragraph 2,

If a member template of a class template is partially specialized, the member template partial specializations are member templates of the enclosing class template; if the enclosing class template is instantiated (13.9.2 [temp.inst], 13.9.3 [temp.explicit]), a declaration for every member template partial specialization is also instantiated as part of creating the members of the class template specialization.

Does this imply that only partial specializations of member templates that are declared before the enclosing class is instantiated are considered? For example, in

  template<typename A> struct X { template<typename B> struct Y; };
  template struct X<int>;
  template<typename A> template<typename B> struct X<A>::Y<B*> { int n; };
  int k = X<int>::Y<int*>().n;

is the last line valid? There is implementation variance on this point. Similarly, for an example like

  template<typename A> struct Outer {
   template<typename B, typename C> struct Inner;
  };
  Outer<int> outer;
  template<typename A> template<typename B>
    struct Outer<A>::Inner<typename A::error, B> {};

at what point, if at all, is the declaration of the partial specialization instantiated? Again, there is implementation variance in the treatment of this example.

Notes from the February, 2014 meeting:

CWG decided that partial specialization declarations should be instantiated only when needed to determine whether the partial specialization matches or not.

Additional note, November, 2014:

See also paper N4090.




1286. Equivalence of alias templates

Section: 13.7.8  [temp.alias]     Status: drafting     Submitter: Gabriel Dos Reis     Date: 2011-04-03

Issue 1244 was resolved by changing the example in 13.6 [temp.type] paragraph 1 from

  template<template<class> class TT> struct X { };
  template<class> struct Y { };
  template<class T> using Z = Y<T>;
  X<Y> y;
  X<Z> z;

to

  template<class T> struct X { };
  template<class> struct Y { };
  template<class T> using Z = Y<T>;
  X<Y<int> > y;
  X<Z<int> > z;

In fact, the original intent was that the example should have been correct as written; however, the normative wording to make it so was missing. The current wording of 13.7.8 [temp.alias] deals only with the equivalence of a specialization of an alias template with the type-id after substitution. Wording needs to be added specifying under what circumstances an alias template itself is equivalent to a class template.

Proposed resolution (September, 2012):

  1. Add the following as a new paragraph following 13.7.8 [temp.alias] paragraph 2:

  2. When the type-id in the declaration of alias template (call it A) consists of a simple-template-id in which the template-argument-list consists of a list of identifiers naming each template-parameter of A exactly once in the same order in which they appear in A's template-parameter-list, the alias template is equivalent to the template named in the simple-template-id (call it T) if A and T have the same number of template-parameters. [Footnote: This rule is transitive: if an alias template A is equivalent to another alias template B that is equivalent to a class template C, then A is also equivalent to C, and A and B are also equivalent to each other. —end footnote] [Example:

      template<typename T, U = T> struct A;
    
      template<typename V, typename W>
        using B = A<V, W>;                // equivalent to A
    
      template<typename V, typename W>
        using C = A<V>;                   // not equivalent to A:
                                          // not all parameters used
    
      template<typename V>
        using D = A<V>;                   // not equivalent to A:
                                          // different number of parameters
    
      template<typename V, typename W>
        using E = A<W, V>;                // not equivalent to A:
                                          // template-arguments in wrong order
    
      template<typename V, typename W = int>
        using F = A<V, W>;                // equivalent to A:
                                          // default arguments not considered
    
      template<typename V, typename W>
        using G = A<V, W>;                // equivalent to A and B
    
      template<typename V, typename W>
        using H = E<V, W>;                // equivalent to E
    
      template<typename V, typename W>
        using I = A<V, typename W::type>; // not equivalent to A:
                                          // argument not identifier
    
    

    end example]

  3. Change 13.6 [temp.type] paragraph 1 as follows:

  4. Two template-ids refer to the same class or function if

    [Example:

    ...declares x2 and x3 to be of the same type. Their type differs from the types of x1 and x4.

      template<class T template<class> class TT> struct X { };
      template<class> struct Y { };
      template<class T> using Z = Y<T>;
      X<Y<int> Y> y;
      X<Z<int> Z> z;
    

    declares y and z to be of the same type. —end example]

Additional note, November, 2014:

Concern has been expressed over the proposed resolution with regard to its handling of default template arguments that differ between the template and its alias, e.g.,

   template<typename T, typename U = int> struct A {};
   template<typename T, typename U = char> using B = A<T, U>;
   template<template<typename...> typename C> struct X { C<int> c; };

Notes from the May, 2015 meeting:

See also issue 1979, which CWG is suggesting to be resolved by defining a “simple” alias, one in which the SFINAE conditions are the same as the referenced template and that uses all template parameters.




1554. Access and alias templates

Section: 13.7.8  [temp.alias]     Status: drafting     Submitter: Jason Merrill     Date: 2012-09-17

The interaction of alias templates and access control is not clear from the current wording of 13.7.8 [temp.alias]. For example:

  template <class T> using foo = typename T::foo;

  class B {
    typedef int foo;
    friend struct C;
  };

  struct C {
    foo<B> f;    // Well-formed?
  };

Is the substitution of B::foo for foo<B> done in the context of the befriended class C, making the reference well-formed, or is the access determined independently of the context in which the alias template specialization appears?

If the answer to this question is that the access is determined independently from the context, care must be taken to ensure that an access failure is still considered to be “in the immediate context of the function type” (13.10.3 [temp.deduct] paragraph 8) so that it results in a deduction failure rather than a hard error.

Notes from the October, 2012 meeting:

The consensus of CWG was that instantiation (lookup and access) for alias templates should be as for other templates, in the definition context rather than in the context where they are used. They should still be expanded immediately, however.

Additional note (February, 2014):

A related problem is raised by the definition of std::enable_if_t (21.3.3 [meta.type.synop]):

  template <bool b, class T = void>
  using enable_if_t = typename enable_if<b,T>::type;

If b is false, there will be no type member. The intent is that such a substitution failure is to be considered as being “in the immediate context” where the alias template specialization is used, but the existing wording does not seem to accomplish that goal.

Additional note, November, 2014:

Concern has been expressed that the intent to analyze access in the context of the alias template definition is at odds with the fact that friendship cannot be granted to alias templates; if it could, the access violation in the original example could be avoided by making foo a friend of class B, but that is not possible.

Additional node, February, 2016:

The issue has been returned to "open" status to facilitate further discussion by CWG as to whether the direction in the October, 2012 note is still desirable.

Notes from the February, 2016 meeting:

CWG reaffirmed the direction described in the October, 2012 note above. With regard to the November, 2014 note regarding granting of friendship, it was observed that the same problem occurs with enumerators, which might refer to inaccessible names in the enumerator volue. The solution in both cases is to embed the declaration in a class and grant the class friendship. See issue 1844, dealing with the definition of “immediate context.”




1979. Alias template specialization in template member definition

Section: 13.7.8  [temp.alias]     Status: drafting     Submitter: Gabriel Dos Reis     Date: 2014-07-31

In an example like

  template<typename T> struct A {
    struct B {
      void f();
    };
  };

  template<typename T> using X = typename A<T>::B;

  template<typename T> void X<T>::f() { }       // #1

should #1 be considered a definition of A<T>::B::f()?

Analogy with alias-declarations would suggest that it should, but alias template specializations involve issues like SFINAE on unused template parameters (see issue 1558) and possibly other complications.

(See also issues 1980, 2021, 2025, and 2037.)

Notes from the May, 2015 meeting:

CWG felt that this kind of usage should be permitted only via a “simple” alias, in which the SFINAE is the same as the template to which it refers and all the template parameters are used. See also issue 1286.




1980. Equivalent but not functionally-equivalent redeclarations

Section: 13.7.8  [temp.alias]     Status: drafting     Submitter: Richard Smith     Date: 2014-08-04

In an example like

  template<typename T, typename U> using X = T;
  template<typename T> X<void, typename T::type> f();
  template<typename T> X<void, typename T::other> f();

it appears that the second declaration of f is a redeclaration of the first but distinguishable by SFINAE, i.e., equivalent but not functionally equivalent.

Notes from the November, 2014 meeting:

CWG felt that these two declarations should not be equivalent.




2236. When is an alias template specialization dependent?

Section: 13.7.8  [temp.alias]     Status: drafting     Submitter: Maxim Kartashev     Date: 2016-03-01

There is implementation divergence for this example:

  struct A { typedef int type; };
  template <typename T> using ALIAS = A;

  template <typename T> void foo()
  {
     ALIAS<T>::type t; // Is typename required here?
  }

  int main()
  {
    foo<A>();
  } 

See also issues 1558, 1979, and 2037.




2462. Problems with the omission of the typename keyword

Section: 13.8.1  [temp.res.general]     Status: drafting     Submitter: Mark Hall     Date: 2020-12-03

According to 13.8.2 [temp.local] paragraph 5,

A qualified-id is assumed to name a type if

There are two possible problems with this specification. First, consider an example like

   template<typename T> struct S {
     static void (*pfunc)(T::name);                               // Omitted typename okay because it is a
                                                                  // member-declaration
   };
   template<typename T> void (*S<T>::pfunc)(T::name) = nullptr;   // Omitted typename ill-formed because not a function
                                                                  // or function template declaration

Should bullet 5.2.4 be extended to include function pointer and member function pointer declarations, as well as function and function template declarations?

Second, given an example like

   template<typename T> struct Y {};
   template<typename T> struct S {
     Y<int(T::type)> m;  // Omitted typename okay because it is in a member-declaration?
  };

Should bullet 5.2.3 be restricted to parameter-declarations of the member being declared, rather than simply “in” such a member-declaration?

Notes from the December, 2020 teleconference:

The second issue was split off into issue 2468 to allow the resolutions to proceed independently.




2468. Omission of the typename keyword in a member template parameter list

Section: 13.8.1  [temp.res.general]     Status: drafting     Submitter: Mark Hall     Date: 2020-12-03

According to 13.8.2 [temp.local] paragraph 5,

A qualified-id is assumed to name a type if

This specification would appear to allow an example like:

   template<typename T> struct Y {};
   template<typename T> struct S {
     Y<int(T::type)> m;  // Omitted typename okay because it is in a member-declaration?
  };

The affected parameter-declarations should be only those of the member declarator, not in a member template's template parameter list.

(Note: this issue was spun off from issue 2462 to allow the resolutions to proceed independently.)




1390. Dependency of alias template specializations

Section: 13.8.3.2  [temp.dep.type]     Status: drafting     Submitter: Johannes Schaub     Date: 2011-09-04

According to 13.8.3.2 [temp.dep.type] paragraph 8, a type is dependent (among other things) if it is

This applies to alias template specializations, even if the resulting type does not depend on the template argument:

    struct B { typedef int type; };
    template<typename> using foo = B;
    template<typename T> void f() {
      foo<T>::type * x;  //error: typename required
    }

Is a change to the rules for cases like this warranted?

Notes from the October, 2012 meeting:

CWG agreed that no typename should be required in this case. In some ways, an alias template specialization is like the current instantiation and can be known at template definition time.




1524. Incompletely-defined class template base

Section: 13.8.3.2  [temp.dep.type]     Status: drafting     Submitter: Jason Merrill     Date: 2012-07-17

The correct handling of an example like the following is unclear:

  template<typename T> struct A {
    struct B: A { };
  };

A type used as a base must be complete (11.7 [class.derived] paragraph 2) . The fact that the base class in this example is the current instantiation could be interpreted as indicating that it should be available for lookup, and thus the normal rule should apply, as members declared after the nested class would not be visible.

On the other hand, 13.8.3 [temp.dep] paragraph 3 says,

In the definition of a class or class template, if a base class depends on a template-parameter, the base class scope is not examined during unqualified name lookup either at the point of definition of the class template or member or during an instantiation of the class template or member.

This wording refers not to a dependent type, which would permit lookup in the current instantiation, but simply to a type that “depends on a template-parameter,” and the current instantiation is such a type.

Implementations vary on the handling of this example.

(See also issue 1526 for another case related to the distinction between a “dependent type” and a “type that depends on a template-parameter.”)

Notes from the October, 2012 meeting:

CWG determined that the example should be ill-formed.




2074. Type-dependence of local class of function template

Section: 13.8.3.2  [temp.dep.type]     Status: drafting     Submitter: Richard Smith     Date: 2015-01-20

According to 13.8.3.2 [temp.dep.type] paragraph 9, a local class in a function template is dependent if and only if it contains a subobject of a dependent type. However, given an example like

  template<typename T> void f() {
    struct X {
      typedef int type;
  #ifdef DEPENDENT
      T x;
  #endif
    };
  X::type y;    // #1
  }
  void g() { f<int>(); }

there is implementation variance in the treatment of #1, but whether or not DEPENDENT is defined appears to make no difference.

In a related question, should a value-dependent alignas specifier cause a type to be dependent? Given

  template<int N> struct Y { typedef int type; };
  template<int N> void h() {
    struct alignas(N) X {};
    Y<alignof(X)>::type z;   // #2
  }
  void i() { h<4>(); }

Most/all implementations issue an error for a missing typename in #2.

Perhaps the right answer is that the types should be dependent but a member of the current instantiation, permitting name lookup without typename.

Additional notes (September, 2022):

At present, the term "current instantiation" is defined for class templates only, and thus does not apply to function templates.

Moreover, the resolution for this issue should also handle local enums, with particular attention to 9.7.2 [enum.udecl] paragraph 1:

The elaborated-enum-specifier shall not name a dependent type and...

This rule, without amendment, would disallow the following reasonable example if local enums were made dependent types:

template <class T>
void f() {
  enum class E { e1, e2 };
  using enum E;
}



2275. Type-dependence of function template

Section: 13.8.3.3  [temp.dep.expr]     Status: drafting     Submitter: Jason Merrill     Date: 2016-06-21

Consider:

  struct B { template <class T> void h(); };
  template <class T> struct A {
    template <class U> static U f(U);
    void g() {
     f(B()).h<int>(); // OK, f(B()) is non-type-dependent with type B.
    }
  }; 

A member template ought to be dependent only if it depends on template parameters of the current scope, but 13.8.3.3 [temp.dep.expr] paragraph 3 is silent on the matter.




2487. Type dependence of function-style cast to incomplete array type

Section: 13.8.3.3  [temp.dep.expr]     Status: drafting     Submitter: Richard Smith     Date: 2021-03-12

Consider:

  using T = int[];
  using U = int[2];
  template<auto M, int ...N> void f() {
    auto &&arr1 = T(N...);
    auto &&arr2 = T{N...};
    auto &&arr3 = U(M, M);
    auto &&arr4 = U{M, M};
  };

I think here T(N...) is not type-dependent, per 13.8.3.3 [temp.dep.expr] paragraph 3, but should be. (I think T{N...} is type-dependent.) Conversely, I think U{M, M} is type-dependent, per 13.8.3.3 [temp.dep.expr] paragraph 6, but should not be. (U(M, M) is not type-dependent.)

I think we should say that

are type-dependent if the type specifier names a dependent type, or if it names an array of unknown bound and the braced-init-list or expression-list is type-dependent.

(I think we could be a little more precise than that in the case where there is no top-level pack expansion: T{M, M} needs to be type-dependent for a general array of unknown bound T due to brace elision, but not in the case where the array element type is a scalar type. And T(M, M) does not need to be type-dependent because direct aggregate initialization can't perform brace elision. But I think the simpler rule is probably good enough.)

Notes from the August, 2021 teleconference:

CWG agreed with the suggested change. There was some support for the “more precise” approach mentioned in the description.




2090. Dependency via non-dependent base class

Section: 13.8.3.5  [temp.dep.temp]     Status: drafting     Submitter: Maxim Kartashev     Date: 2015-02-27

According to 13.8.3.5 [temp.dep.temp] paragraph 3,

a non-type template-argument is dependent if the corresponding non-type template-parameter is of reference or pointer type and the template-argument designates or points to a member of the current instantiation or a member of a dependent type.

Members of non-dependent base classes are members of the current instantiation, but using one as a non-type template argument should not be considered dependent.




2. How can dependent names be used in member declarations that appear outside of the class template definition?

Section: 13.8.4  [temp.dep.res]     Status: drafting     Submitter: unknown     Date: unknown
    template <class T> class Foo {

       public:
       typedef int Bar;
       Bar f();
    };
    template <class T> typename Foo<T>::Bar Foo<T>::f() { return 1;}
                       --------------------
In the class template definition, the declaration of the member function is interpreted as:
   int Foo<T>::f();
In the definition of the member function that appears outside of the class template, the return type is not known until the member function is instantiated. Must the return type of the member function be known when this out-of-line definition is seen (in which case the definition above is ill-formed)? Or is it OK to wait until the member function is instantiated to see if the type of the return type matches the return type in the class template definition (in which case the definition above is well-formed)?

Suggested resolution: (John Spicer)

My opinion (which I think matches several posted on the reflector recently) is that the out-of-class definition must match the declaration in the template. In your example they do match, so it is well formed.

I've added some additional cases that illustrate cases that I think either are allowed or should be allowed, and some cases that I don't think are allowed.

    template <class T> class A { typedef int X; };


    template <class T> class Foo {
     public:
       typedef int Bar;
       typedef typename A<T>::X X;
       Bar f();
       Bar g1();
       int g2();
       X h();
       X i();
       int j();
     };

     // Declarations that are okay
     template <class T> typename Foo<T>::Bar Foo<T>::f()
                                                     { return 1;}
     template <class T> typename Foo<T>::Bar Foo<T>::g1()
                                                     { return 1;}
     template <class T> int Foo<T>::g2() { return 1;}
     template <class T> typename Foo<T>::X Foo<T>::h() { return 1;}

     // Declarations that are not okay
     template <class T> int Foo<T>::i() { return 1;}
     template <class T> typename Foo<T>::X Foo<T>::j() { return 1;}
In general, if you can match the declarations up using only information from the template, then the declaration is valid.

Declarations like Foo::i and Foo::j are invalid because for a given instance of A<T>, A<T>::X may not actually be int if the class is specialized.

This is not a problem for Foo::g1 and Foo::g2 because for any instance of Foo<T> that is generated from the template you know that Bar will always be int. If an instance of Foo is specialized, the template member definitions are not used so it doesn't matter whether a specialization defines Bar as int or not.




287. Order dependencies in template instantiation

Section: 13.8.4.1  [temp.point]     Status: drafting     Submitter: Martin Sebor     Date: 17 May 2001

Implementations differ in their treatment of the following code:

    template <class T>
    struct A {
	typename T::X x;
    };

    template <class T>
    struct B {
	typedef T* X;
	A<B> a;
    };

    int main ()
    {
	B<int> b;
    }

Some implementations accept it. At least one rejects it because the instantiation of A<B<int> > requires that B<int> be complete, and it is not at the point at which A<B<int> > is being instantiated.

Erwin Unruh:

In my view the programm is ill-formed. My reasoning:

So each class needs the other to be complete.

The problem can be seen much easier if you replace the typedef with

    typedef T (*X) [sizeof(B::a)];

Now you have a true recursion. The compiler cannot easily distinguish between a true recursion and a potential recursion.

John Spicer:

Using a class to form a qualified name does not require the class to be complete, it only requires that the named member already have been declared. In other words, this kind of usage is permitted:

    class A {
        typedef int B;
        A::B ab;
    };

In the same way, once B has been declared in A, it is also visible to any template that uses A through a template parameter.

The standard could be more clear in this regard, but there are two notes that make this point. Both 6.5.5.2 [class.qual] and _N4567_.5.1.1 [expr.prim.general] paragraph 7 contain a note that says "a class member can be referred to using a qualified-id at any point in its potential scope (6.4.7 [basic.scope.class])." A member's potential scope begins at its point of declaration.

In other words, a class has three states: incomplete, being completed, and complete. The standard permits a qualified name to be used once a name has been declared. The quotation of the notes about the potential scope was intended to support that.

So, in the original example, class A does not require the type of T to be complete, only that it have already declared a member X.

Bill Gibbons:

The template and non-template cases are different. In the non-template case the order in which the members become declared is clear. In the template case the members of the instantiation are conceptually all created at the same time. The standard does not say anything about trying to mimic the non-template case during the instantiation of a class template.

Mike Miller:

I think the relevant specification is 13.8.4.1 [temp.point] paragraph 3, dealing with the point of instantiation:

For a class template specialization... if the specialization is implicitly instantiated because it is referenced from within another template specialization, if the context from which the specialization is referenced depends on a template parameter, and if the specialization is not instantiated previous to the instantiation of the enclosing template, the point of instantiation is immediately before the point of instantiation of the enclosing template. Otherwise, the point of instantiation for such a specialization immediately precedes the namespace scope declaration or definition that refers to the specialization.

That means that the point of instantiation of A<B<int> > is before that of B<int>, not in the middle of B<int> after the declaration of B::X, and consequently a reference to B<int>::X from A<B<int> > is ill-formed.

To put it another way, I believe John's approach requires that there be an instantiation stack, with the results of partially-instantiated templates on the stack being available to instantiations above them. I don't think the Standard mandates that approach; as far as I can see, simply determining the implicit instantiations that need to be done, rewriting the definitions at their respective points of instantiation with parameters substituted (with appropriate "forward declarations" to allow for non-instantiating references), and compiling the result normally should be an acceptable implementation technique as well. That is, the implicit instantiation of the example (using, e.g., B_int to represent the generated name of the B<int> specialization) could be something like

        struct B_int;

        struct A_B_int {
            B_int::X x;    // error, incomplete type
        };

        struct B_int {
            typedef int* X;
            A_B_int a;
        };

Notes from 10/01 meeting:

This was discussed at length. The consensus was that the template case should be treated the same as the non-template class case it terms of the order in which members get declared/defined and classes get completed.

Proposed resolution:

In 13.8.4.1 [temp.point] paragraph 3 change:

the point of instantiation is immediately before the point of instantiation of the enclosing template. Otherwise, the point of instantiation for such a specialization immediately precedes the namespace scope declaration or definition that refers to the specialization.

To:

the point of instantiation is the same as the point of instantiation of the enclosing template. Otherwise, the point of instantiation for such a specialization immediately precedes the nearest enclosing declaration. [Note: The point of instantiation is still at namespace scope but any declarations preceding the point of instantiation, even if not at namespace scope, are considered to have been seen.]

Add following paragraph 3:

If an implicitly instantiated class template specialization, class member specialization, or specialization of a class template references a class, class template specialization, class member specialization, or specialization of a class template containing a specialization reference that directly or indirectly caused the instantiation, the requirements of completeness and ordering of the class reference are applied in the context of the specialization reference.

and the following example

  template <class T> struct A {
          typename T::X x;
  };

  struct B {
          typedef int X;
          A<B> a;
  };

  template <class T> struct C {
          typedef T* X;
          A<C> a;
  };

  int main ()
  {
          C<int> c;
  }

Notes from the October 2002 meeting:

This needs work. Moved back to drafting status.

See also issues 595 and 1330.




1845. Point of instantiation of a variable template specialization

Section: 13.8.4.1  [temp.point]     Status: drafting     Submitter: Richard Smith     Date: 2014-01-28

The current wording of 13.8.4.1 [temp.point] does not define the point of instantiation of a variable template specialization. Presumably replacing the references to “static data member of a class template” with “variable template” in paragraphs 1 and 8 would be sufficient.

Additional note, July, 2017:

It has also been observed that there is no definition of the point of instantiation for an alias template. It is not clear that there is a need for normative wording for the point of instantiation of an alias template, but if not, a note explaining its absence would be helpful.




2245. Point of instantiation of incomplete class template

Section: 13.8.4.1  [temp.point]     Status: drafting     Submitter: Richard Smith     Date: 2016-03-08

Consider:

  template<typename T> struct X;

  extern X<int> *p;
  void *q = +p; // #1, complete type affects semantics via ADL

  template<typename T> struct X {};
  X<int> x; // #2, ill-formed, X<int> is incomplete

According to the wording of issue 212, this program is ill-formed, because the single point of instantiation for X<int> is at #1, thus X<int> is an incomplete type even at #2 after the primary template has been completed.

Notes from the December, 2016 teleconference:

The consensus was that references to specializations before the template definition is seen are not points of instantiation.




2497. Points of instantiation for constexpr function templates

Section: 13.8.4.1  [temp.point]     Status: drafting     Submitter: Richard Smith     Date: 2019-07-20

Consider:

  template<typename T> constexpr T f();
  constexpr int g() { return f<int>(); } // #1
  template<typename T> constexpr T f() { return 123; }
  int k[g()];
  // #2

There are two points of instantiation for f<int>. At #1, the template isn't defined, so it cannot be instantiated there. At #2, it's too late, as the definition was needed when parsing the type of k.

Should we also treat the point of definition of (at least) a constexpr function template as a point of instantiation for all specializations that have a point of instantiation before that point? Note the possible interaction of such a resolution with 13.8.4.1 [temp.point] paragraph 7:

If two different points of instantiation give a template specialization different meanings according to the one-definition rule (6.3 [basic.def.odr]), the program is ill-formed, no diagnostic required.

Notes from the November, 2021 teleconference:

Another possibility for a point of instantiation, other than the definition of the template, would be the point at which the function is called. Similar questions have been raised regarding the points at which variables are initialized (issue 2186) and constexpr functions are defined (issue 2166).




2202. When does default argument instantiation occur?

Section: 13.9.2  [temp.inst]     Status: drafting     Submitter: Richard Smith     Date: 2015-11-19

According to 13.9.2 [temp.inst] paragraph 11,

If a function template f is called in a way that requires a default argument to be used, the dependent names are looked up, the semantics constraints are checked, and the instantiation of any template used in the default argument is done as if the default argument had been an initializer used in a function template specialization with the same scope, the same template parameters and the same access as that of the function template f used at that point, except that the scope in which a closure type is declared (7.5.5.2 [expr.prim.lambda.closure]) — and therefore its associated namespaces — remain as determined from the context of the definition for the default argument. This analysis is called default argument instantiation. The instantiated default argument is then used as the argument of f.

Some details are not clear from this description. For example, given

  #include <type_traits>
  template<class T> struct Foo { Foo(T = nullptr) {} };
  bool b = std::is_constructible<Foo<int>>::value;
  int main() {}

does “used” mean odr-used or used in any way? Is a failure of default argument instantiation in the immediate context of the call or is a failure a hard error? And does it apply only to function templates, as it says, or should it apply to member functions of class templates? There is implementation divergence on these questions.

Notes from the March, 2018 meeting:

CWG felt that such errors should be substitution failures, not hard errors.




2222. Additional contexts where instantiation is not required

Section: 13.9.2  [temp.inst]     Status: drafting     Submitter: CWG     Date: 2016-01-11

According to 13.9.2 [temp.inst] paragraph 6,

If the function selected by overload resolution (12.2 [over.match]) can be determined without instantiating a class template definition, it is unspecified whether that instantiation actually takes place.

There are other contexts in which a smart implementation could presumably avoid instantiations, such as when doing argument-dependent lookup involving a class template specialization when the template definition contains no friend declarations or checking base/derived relationships involving incomplete class template definitions. It would be helpful to enumerate such contexts.




2263. Default argument instantiation for friends

Section: 13.9.2  [temp.inst]     Status: drafting     Submitter: Hubert Tong     Date: 2016-05-04

The instantiation of default arguments for friends defined in a templated entity is not covered by 13.7.1 [temp.decls.general] paragraph 3 or 13.9.2 [temp.inst] paragraph 2. Consider:

  template <typename T>
  struct A {
    friend void foo(A &&, int = T::happy) { }
  };

  int main(void) { foo(A<int>(), 0); }

There is implementation divergence in the treatment of this example.

Notes from the December, 2016 teleconference:

This issue should be resolved by the resolution of issue 2174.




2265. Delayed pack expansion and member redeclarations

Section: 13.9.2  [temp.inst]     Status: drafting     Submitter: Hubert Tong     Date: 2016-05-11

It is not clear how to handle parameter packs that are expanded during instantiation in parallel with those that are not yet concrete. In particular, does the following example require a diagnostic?

  template<typename ...T> struct Tuple;
  template<class T, class U> struct Outer;
  template<class ...T, class ...U>
  struct Outer<Tuple<T ...>, Tuple<U ...> > {
    template<class X, class Y> struct Inner;
    template<class ...Y> struct Inner<Tuple<T, Y> ...> { };
    template<class ...Y> struct Inner<Tuple<U, Y> ...> { };
  };
  Outer<Tuple<int, void>, Tuple<int, void> > outer;

Notes from the March, 2018 meeting:

CWG felt that ill-formed, no diagnostic required was the correct approach.




2596. Instantiation of constrained non-template friends

Section: 13.9.2  [temp.inst]     Status: drafting     Submitter: David Friberg     Date: 2022-06-03

Consider:

  struct Base {};

  template<int N>
  struct S : public Base {
    friend int foo(Base&) requires (N == 1) { return 1; }
    friend int foo(Base&) requires (N == 2) { return 3; }
  };

  int main() {
    S<1> s1{};
    S<2> s2{};  // #1
  }

The current wording does not seem to cover what happens for this case. In particular, 13.9.2 [temp.inst] paragraph 17 does not cover constrained non-template friends.

See also the Itanium ABI issue 24.

Suggested resolution:

  1. Change in 13.7.5 [temp.friend] paragraph 9 as follows:

    A non-template friend declaration with a requires-clause shall be a definition. A friend function template with a constraint that depends on a template parameter from an enclosing template shall be a definition. Such a constrained friend function or function template declaration does not declare the same function or function template as a declaration in inhabiting any other scope.
  2. Change in 13.9.2 [temp.inst] paragraph 17 as follows:

    The type-constraints and requires-clause of a template specialization or member templated function are not instantiated along with the specialization or function itself, even for a member function of a local class; substitution into the atomic constraints formed from them is instead performed as specified in 13.5.3 [temp.constr.decl] and 13.5.2.3 [temp.constr.atomic] when determining whether the constraints are satisfied or as specified in 13.5.3 [temp.constr.decl] when comparing declarations.

    [ Note 7: ... ]

    [ Example 10: ... ]

    [ Example:

      struct Base {};
    
      template<int N>
      struct S : Base {
        friend int foo(Base&) requires (N == 1) { return 1; }  // #1
        friend int foo(Base&) requires (N == 2) { return 3; }  // #2
      };
      S<1> s1;
      S<2> s2;          // OK, no conflict between #1 and #2
      int x = foo(s1);  // OK, selects #1
      int y = foo(s2);  // OK, selects #2
    

    -- end example ]

    [ Example 11: ... ]

CWG 2022-11-10

The friend definitions should conflict with friend definitions from other instantiations of the same class template, consistent with how non-constrained friends would work. Note that the enclosing dependent class type does not appear in the friend function's signature, which is unusual.




1665. Declaration matching in explicit instantiations

Section: 13.9.3  [temp.explicit]     Status: drafting     Submitter: Richard Smith     Date: 2013-04-19

Consider a case like

  struct X {
    template<typename T> void f(T);
    void f(int);
  };
  template void X::f(int);

or

  template<typename T> void f(T) {}
  void f(int);
  template void f(int);

Presumably in both these cases the explicit instantiation should refer to the template and not to the non-template; however, 13.7.3 [temp.mem] paragraph 2 says,

A normal (non-template) member function with a given name and type and a member function template of the same name, which could be used to generate a specialization of the same type, can both be declared in a class. When both exist, a use of that name and type refers to the non-template member unless an explicit template argument list is supplied.

This would appear to give the wrong answer for the first example. It's not clearly stated, but consistency would suggest a similar wrong answer for the second. Presumably a statement is needed somewhere that an explicit instantiation directive applies to a template and not a non-template function if both are visible.

Additional note, January, 2014:

A related example has been raised:

  template<typename T> class Matrix {
  public:
    Matrix(){}
    Matrix(const Matrix&){}
    template<typename U>
      Matrix(const Matrix<U>&);
  };

  template Matrix<int>::Matrix(const Matrix&);

  Matrix<int> m;
  Matrix<int> mm(m);

If the explicit instantiation directive applies to the constructor template, there is no way to explicitly instantiate the copy constructor.




2421. Explicit instantiation of constrained member functions

Section: 13.9.3  [temp.explicit]     Status: drafting     Submitter: Casey Carter     Date: 2019-07-16

An explicit instantiation of a class template specialization also explicitly instantiates member functions of that class template specialization whose constraints are satisfied, even those that are not callable because a more-constrained overload exists which would always be selected by overload resolution. Ideally, we would not explicitly instantiate definitions of such uncallable functions.

Notes from the August, 2020 teleconference:

CWG felt that the concept of “eligible” might form a basis for the resolution of this issue.




2501. Explicit instantiation and trailing requires-clauses

Section: 13.9.3  [temp.explicit]     Status: drafting     Submitter: Davis Herring     Date: 2021-08-09

CWG determined that issue 2488 was not a defect. However, the discussion uncovered an issue regarding the handling of an explicit instantiation of a class template containing such members. According to 13.9.3 [temp.explicit] paragraph 10,

An explicit instantiation that names a class template specialization is also an explicit instantiation of the same kind (declaration or definition) of each of its direct non-template members that has not been previously explicitly specialized in the translation unit containing the explicit instantiation, provided that the associated constraints, if any, of that member are satisfied by the template arguments of the explicit instantiation (13.5.3 [temp.constr.decl], 13.5.2 [temp.constr.constr]), except as described below.

Paragraph 12 says,

An explicit instantiation of a prospective destructor (11.4.7 [class.dtor]) shall correspond to the selected destructor of the class.

Perhaps the virtual and constrained members could be handled in an analogous fashion.

Notes from the November, 2021 teleconference:

Issue 2488 is being reopened due to subsequent comments.

CWG 2022-11-10

For each explicit instantiation, there shall be exactly one member whose constraints are more specialized than any other member with the same signature. Use the "address of function" model to determine this member.




529. Use of template<> with “explicitly-specialized” class templates

Section: 13.9.4  [temp.expl.spec]     Status: drafting     Submitter: James Widman     Date: 16 August 2005

Paragraph 17 of 13.9.4 [temp.expl.spec] says,

A member or a member template may be nested within many enclosing class templates. In an explicit specialization for such a member, the member declaration shall be preceded by a template<> for each enclosing class template that is explicitly specialized.

This is curious, because paragraph 3 only allows explicit specialization of members of implicitly-instantiated class specializations, not explicit specializations. Furthermore, paragraph 4 says,

Definitions of members of an explicitly specialized class are defined in the same manner as members of normal classes, and not using the explicit specialization syntax.

Paragraph 18 provides a clue for resolving the apparent contradiction:

In an explicit specialization declaration for a member of a class template or a member template that appears in namespace scope, the member template and some of its enclosing class templates may remain unspecialized, except that the declaration shall not explicitly specialize a class member template if its enclosing class templates are not explicitly specialized as well. In such explicit specialization declaration, the keyword template followed by a template-parameter-list shall be provided instead of the template<> preceding the explicit specialization declaration of the member.

It appears from this and the following example that the phrase “explicitly specialized” in paragraphs 17 and 18, when referring to enclosing class templates, does not mean that explicit specializations have been declared for them but that their names in the qualified-id are followed by template argument lists. This terminology is confusing and should be changed.

Proposed resolution (October, 2005):

  1. Change 13.9.4 [temp.expl.spec] paragraph 17 as indicated:

  2. A member or a member template may be nested within many enclosing class templates. In an explicit specialization for such a member, the member declaration shall be preceded by a template<> for each enclosing class template that is explicitly specialized specialization. [Example:...
  3. Change 13.9.4 [temp.expl.spec] paragraph 18 as indicated:

  4. In an explicit specialization declaration for a member of a class template or a member template that appears in namespace scope, the member template and some of its enclosing class templates may remain unspecialized, except that the declaration shall not explicitly specialize a class member template if its enclosing class templates are not explicitly specialized as well that is, the template-id naming the template may be composed of template parameter names rather than template-arguments. In For each unspecialized template in such an explicit specialization declaration, the keyword template followed by a template-parameter-list shall be provided instead of the template<> preceding the explicit specialization declaration of the member. The types of the template-parameters in the template-parameter-list shall be the same as those specified in the primary template definition. In such declarations, an unspecialized template-id shall not precede the name of a template specialization in the qualified-id naming the member. [Example:...

Notes from the April, 2006 meeting:

The revised wording describing “unspecialized” templates needs more work to ensure that the parameter names in the template-id are in the correct order; the distinction between template arguments and parameters is also probably not clear enough. It might be better to replace this paragraph completely and avoid the “unspecialized” wording altogether.

Proposed resolution (February, 2010):

  1. Change 13.9.4 [temp.expl.spec] paragraph 17 as follows:

  2. A member or a member template may be nested within many enclosing class templates. In an explicit specialization for such a member, the member declaration shall be preceded by a template<> for each enclosing class template that is explicitly specialized specialization. [Example:...
  3. Change 13.9.4 [temp.expl.spec] paragraph 18 as follows:

  4. In an explicit specialization declaration for a member of a class template or a member template that appears in namespace scope, the member template and some of its enclosing class templates may remain unspecialized, except that the declaration shall not explicitly specialize a class member template if its enclosing class templates are not explicitly specialized as well. In such explicit specialization declaration, the keyword template followed by a template-parameter-list shall be provided instead of the template<> preceding the explicit specialization declaration of the member. The types of the template-parameters in the template-parameter-list shall be the same as those specified in the primary template definition. that is, the corresponding template prefix may specify a template-parameter-list instead of template<> and the template-id naming the template be written using those template-parameters as template-arguments. In such a declaration, the number, kinds, and types of the template-parameters shall be the same as those specified in the primary template definition, and the template-parameters shall be named in the template-id in the same order that they appear in the template-parameter-list. An unspecialized template-id shall not precede the name of a template specialization in the qualified-id naming the member. [Example:...



1840. Non-deleted explicit specialization of deleted function template

Section: 13.9.4  [temp.expl.spec]     Status: drafting     Submitter: Richard Smith     Date: 2014-01-19

The resolution of issue 941 permits a non-deleted explicit specialization of a deleted function template. For example:

  template<typename T> void f() = delete;
  decltype(f<int>()) *p;
  template<> void f<int>();

However, the existing normative wording is not adequate to handle this usage. For one thing, =delete is formally, at least, a function definition, and an implementation is not permitted to instantiate a function definition unless it is used; presumably, then, an implementation could not reject the decltype above as a reference to a deleted specialization. Furthermore, there should be a requirement that a non-deleted explicit specialization of a deleted function template must precede any reference to that specialization. (I.e., the example should be ill-formed as written but well-formed if the last two lines were interchanged.)




1993. Use of template<> defining member of explicit specialization

Section: 13.9.4  [temp.expl.spec]     Status: drafting     Submitter: Richard Smith     Date: 2014-08-31

Issue 531 surveyed existing practice at the time and determined that the most common syntax for defining a member of an explicit specialization used the template<> prefix. This approach, however, does not seem consistent, since such a definition is not itself an explicit specialization.




2409. Explicit specializations of constexpr static data members

Section: 13.9.4  [temp.expl.spec]     Status: drafting     Submitter: Mike Miller     Date: 2019-04-29

The status of an example like the following is not clear:

  struct S {
    template <int N> static constexpr inline int m = N;
  };
  template <> constexpr inline int S::m<5>;

Some implementations accept this, apparently on the basis of allowing and ignoring a redeclaration of a constexpr static data member outside its class, although there is implementation divergence. Most or all implementations, however, diagnose an attempt to use such a specialization in a constant context.

Should it be required to have a definition of the explicit specialization in order to declare it outside the class in such cases?

In addition, most or all implementations accept a version of the example in which the explicit specialization contains an initializer, including allowing its use in constant contexts:

  template <> constexpr inline int S::m<5> = 2;

This would seem to be disallowed both by 11.4.9.3 [class.static.data] paragraph 3,

An inline static data member may be defined in the class definition and may specify a brace-or-equal-initializer. If the member is declared with the constexpr specifier, it may be redeclared in namespace scope with no initializer (this usage is deprecated; see _N4778_.D.4 [depr.static_constexpr]).

which prohibits an initializer, and 13.9.4 [temp.expl.spec] paragraph 2,

An explicit specialization may be declared in any scope in which the corresponding primary template may be defined (_N4868_.9.8.2.3 [namespace.memdef], 11.4 [class.mem], 13.7.3 [temp.mem]).

since the definition of a constexpr static data member is inside the class.

Notes from the May, 2019 teleconference:

These examples should behave in the same way as if the class were templated: instantiate the declaration and the definition of the static data member separately. The first example should be ill-formed, because the explicit specializaation does not have an initializer.




2055. Explicitly-specified non-deduced parameter packs

Section: 13.10.2  [temp.arg.explicit]     Status: drafting     Submitter: Jonathan Caves     Date: 2014-12-09

According to 13.10.2 [temp.arg.explicit] paragraph 3,

Trailing template arguments that can be deduced (13.10.3 [temp.deduct]) or obtained from default template-arguments may be omitted from the list of explicit template-arguments. A trailing template parameter pack (13.7.4 [temp.variadic]) not otherwise deduced will be deduced to an empty sequence of template arguments. If all of the template arguments can be deduced, they may all be omitted; in this case, the empty template argument list <> itself may also be omitted. In contexts where deduction is done and fails, or in contexts where deduction is not done, if a template argument list is specified and it, along with any default template arguments, identifies a single function template specialization, then the template-id is an lvalue for the function template specialization.

It is not clear that this permits an example like:

  template<typename... T> void f(typename T::type...)   {
  }

  int main() {
    f<>();
  }

See also issue 2105.




1172. “instantiation-dependent” constructs

Section: 13.10.3  [temp.deduct]     Status: drafting     Submitter: Adamczyk     Date: 2010-08-05

There are certain constructs that are not covered by the existing categories of “type dependent” and “value dependent.” For example, the expression sizeof(sizeof(T())) is neither type-dependent nor value-dependent, but its validity depends on whether T can be value-constructed. We should be able to overload on such characteristics and select via deduction failure, but we need a term like “instantiation-dependent” to describe these cases in the Standard. The phrase “expression involving a template parameter” seems to come pretty close to capturing this idea.

Notes from the November, 2010 meeting:

The CWG favored extending the concepts of “type-dependent” and “value-dependent” to cover these additional cases, rather than adding a new concept.

Notes from the March, 2011 meeting:

The CWG reconsidered the direction from the November, 2010 meeting, as it would make more constructs dependent, thus requiring more template and typename keywords, resulting in worse error messages, etc.

Notes from the August, 2011 meeting:

The following example (from issue 1273) was deemed relevant for this issue:

    template <class T> struct C;

    class A {
       int i;
       friend struct C<int>;
    } a;

    class B {
       int i;
       friend struct C<float>;
    } b;

    template <class T>
    struct C {
       template <class U> decltype (a.i) f() { } // #1
       template <class U> decltype (b.i) f() { } // #2
    };

    int main() {
       C<int>().f<int>();     // calls #1
       C<float>().f<float>(); // calls #2
    }



1322. Function parameter type decay in templates

Section: 13.10.3  [temp.deduct]     Status: drafting     Submitter: Jason Merrill     Date: 2011-05-19

The discussion of issue 1001 seemed to have settled on the approach of doing the 9.3.4.6 [dcl.fct] transformations immediately to the function template declaration, so that the original form need not be remembered. However, the example in 13.10.3 [temp.deduct] paragraph 8 suggests otherwise:

  template <class T> int f(T[5]);
  int I = f<int>(0);
  int j = f<void>(0); // invalid array

One way that might be addressed would be to separate the concepts of the type of the template that participates in overload resolution and function matching from the type of the template that is the source for template argument substitution. (See also the example in paragraph 3 of the same section.)

Notes, January, 2012:




1582. Template default arguments and deduction failure

Section: 13.10.3  [temp.deduct]     Status: drafting     Submitter: John Spicer     Date: 2012-10-31

According to 13.10.3 [temp.deduct] paragraph 5,

The resulting substituted and adjusted function type is used as the type of the function template for template argument deduction. If a template argument has not been deduced and its corresponding template parameter has a default argument, the template argument is determined by substituting the template arguments determined for preceding template parameters into the default argument. If the substitution results in an invalid type, as described above, type deduction fails.

This leaves the impression that default arguments are used after deduction failure leaves an argument undeduced. For example,

  template<typename T> struct Wrapper;
  template<typename T = int> void f(Wrapper<T>*);
  void g() {
    f(0);
  }

Deduction fails for T, so presumably int is used. However, some implementations reject this code. It appears that the intent would be better expressed as something like

...If a template argument is used only in a non-deduced context and its corresponding template parameter has a default argument...

Rationale (November, 2013):

CWG felt that this issue should be considered by EWG in a broader context before being resolved.

Additional note, April, 2015:

EWG has requested that CWG resolve this issue along the lines discussed above.

Notes from the May, 2015 meeting:

CWG agreed that a default template argument should only be used if the parameter is not used in a deducible context. See also issue 2092.




1513. initializer_list deduction failure

Section: 13.10.3.2  [temp.deduct.call]     Status: drafting     Submitter: Steve Adamczyk     Date: 2012-06-28

According to 13.10.3.2 [temp.deduct.call] paragraph 1,

If removing references and cv-qualifiers from P gives std::initializer_list<P'> for some P' and the argument is an initializer list (9.4.5 [dcl.init.list]), then deduction is performed instead for each element of the initializer list, taking P' as a function template parameter type and the initializer element as its argument. Otherwise, an initializer list argument causes the parameter to be considered a non-deduced context (13.10.3.6 [temp.deduct.type]).

It is not entirely clear whether the deduction for an initializer list meeting a std::initializer_list<T> is a recursive subcase, or part of the primary deduction. A relevant question is: if the deduction on that part fails, does the entire deduction fail, or is the parameter to be considered non-deduced?

See also issue 2326.

Notes from the October, 2012 meeting:

CWG determined that the entire deduction fails in this case.




1584. Deducing function types from cv-qualified types

Section: 13.10.3.2  [temp.deduct.call]     Status: drafting     Submitter: Daniel Krügler     Date: 2012-11-04

It is not clear whether the following is well-formed or not:

  void foo(){}

  template<class T>
  void deduce(const T*) { }

  int main() {
    deduce(foo);
  }

Implementations vary in their treatment of this example.

Proposed resolution (April, 2013):

Change 13.10.3.6 [temp.deduct.type] paragraph 18 as follows:

A template-argument can be deduced from a function, pointer to function, or pointer to member function type. [Note: cv-qualification of a deduced function type is ignored; see 9.3.4.6 [dcl.fct]. —end note] [Example:

  template<class T> void f(void(*)(T,int));
  template<class T> void f2(const T*);
  template<class T> void foo(T,int);
  void g(int,int);
  void g(char,int);
  void g2();

  void h(int,int,int);
  void h(char,int);
  int m() {
    f(&g);     // error: ambiguous
    f(&h);     // OK: void h(char,int) is a unique match
    f(&foo);   // error: type deduction fails because foo is a template
    f2(g2);    // OK: cv-qualification of deduced function type ignored
  }

end example]

Additional note, November, 2014:

Concern was expressed regarding the proposed resolution over its treatment of an example like the following:

  template<typename T> struct tuple_size {};
  template<typename T> struct tuple_size<T const>: tuple_size<T> {};

  tuple_size<void()> t;

In this case T const is always considered to be more specialized for void(), leading to infinite self-derivation.

The issue has been returned to "open" status for further consideration.

Notes from the May, 2015 meeting:

The consensus of CWG was that the cv-qualification of the argument and parameter must match, so the original example should be rejected.




1486. Base-derived conversion in member pointer deduction

Section: 13.10.3.3  [temp.deduct.funcaddr]     Status: drafting     Submitter: John Spicer     Date: 2012-03-26

The rules for deducing template arguments when taking the address of a function template in 13.10.3.3 [temp.deduct.funcaddr] do not appear to allow for a base-to-derived conversion in a case like:

  struct Base {
    template<class U> void f(U);
  };

  struct Derived : Base { };

  int main() {
    void (Derived::*pmf)(int) = &Derived::f;
  }

Most implementations appear to allow this adjustment, however.




1610. Cv-qualification in deduction of reference to array

Section: 13.10.3.5  [temp.deduct.partial]     Status: drafting     Submitter: Richard Smith     Date: 2013-01-28

Given

   template<class C> void foo(const C* val) {}
   template<int N> void foo(const char (&t)[N]) {}

it is intuitive that the second template is more specialized than the first. However, the current rules make them unordered. In 13.10.3.5 [temp.deduct.partial] paragraph 4, we have P as const C* and A as const char (&)[N]. Paragraph 5 transforms A to const char[N]. Finally, paragraph 7 removes top-level cv-qualification; since a cv-qualified array element type is considered to be cv-qualification of the array (6.8.5 [basic.type.qualifier] paragraph 5, cf issue 1059), A becomes char[N]. P remains const C*, so deduction fails because of the missing const in A.

Notes from the April, 2013 meeting:

CWG agreed that the const should be preserved in the array type.




2328. Unclear presentation style of template argument deduction rules

Section: 13.10.3.6  [temp.deduct.type]     Status: drafting     Submitter: Richard Smith     Date: 2016-10-11

The presentation style of 13.10.3.6 [temp.deduct.type] paragraph 8 results in a specification that is unclear, needlessly verbose, and incomplete. Specific problems include:




2172. Multiple exceptions with one exception object

Section: 14.4  [except.handle]     Status: drafting     Submitter: Richard Smith     Date: 2015-09-14

During the discussion of issue 2098 it was observed that multiple exceptions may share a single exception object via std::exception_ptr. It is not clear that the current wording handles that case correctly.




2219. Dynamically-unreachable handlers

Section: 14.4  [except.handle]     Status: drafting     Submitter: 2016-01-04     Date: Richard Smith

Consider the following example:

  #include <cstdio>
  #include <cstdlib>

  void f() {
    struct X {
     ~X() {
       std::puts("unwound");
       std::exit(0);
     }
    } x;
    throw 0;
  }

  int main(int argc, char**) {
    try {
      f();
    } catch (int) {
      std::puts("caught");
    }
  }

According to the Standard, this should print unwound and exit. Current optimizing implementations call terminate(), because:

More abstractly, before calling terminate, we're required to check whether there is an active handler for an exception of type int, and in some sense there is not (because the handler in main is dynamically unreachable).

There seem to be three possible solutions:

  1. Change the standard to say that terminate() is a valid response to this situation [this seems problematic, as any non-returning destructor now risks program termination, but is in fact the status quo on multiple implementations and does not seem to have resulted in any bug reports]

  2. Always fully unwind before calling terminate() [this significantly harms debugability of exceptions]

  3. Teach the compilers to not optimize out unreachable exception handlers [for some implementations, this is “remove, redesign and reimplement middle-end support for EH”-level difficult, and harms the ability to optimize code involving catch handlers]




1718. Macro invocation spanning end-of-file

Section: 15.6  [cpp.replace]     Status: drafting     Submitter: David Krauss     Date: 2013-07-23     Liaison: WG14

Although it seems to be common implementation practice to reject a macro invocation that begins in a header file and whose closing right parenthesis appears in the file that included it, there does not seem to be a prohibition of this case in the specification of function-style macros. Should this be accepted?

Notes from the February, 2014 meeting:

CWG agreed that macro invocations spanning file boundaries should be prohibited. Resolution of this issue should be coordinated with WG14.




2003. Zero-argument macros incorrectly specified

Section: 15.6  [cpp.replace]     Status: drafting     Submitter: Richard Smith     Date: 2014-09-12

According to 15.6 [cpp.replace] paragraph 4,

If the identifier-list in the macro definition does not end with an ellipsis, the number of arguments (including those arguments consisting of no preprocessing tokens) in an invocation of a function-like macro shall equal the number of parameters in the macro definition.

That is, a sequence of no preprocessing tokens counts as an argument. That phrasing has problems with zero-argument function-like macros, e.g.,

  #define M()
  M();

M is defined as having no parameters but the invocation has one (empty) argument, which does not match the number of parameters in the definition.




1709. Stringizing raw string literals containing newline

Section: 15.6.3  [cpp.stringize]     Status: drafting     Submitter: David Krauss     Date: 2013-07-01

Stringizing a raw string literal containing a newline produces an invalid (unterminated) string literal and hence results in undefined behavior. It should be specified that a newline in a string literal is transformed to the two characters '\' 'n' in the resulting string literal.

A slightly related case involves stringizing a bare backslash character: because backslashes are only escaped within a string or character literal, a stringized bare backslash becomes "\", which is invalid and hence results in undefined behavior.




1889. Unclear effect of #pragma on conformance

Section: 15.9  [cpp.pragma]     Status: drafting     Submitter: James Widman     Date: 2014-03-05

According to 15.9 [cpp.pragma] paragraph 1, the effect of a #pragma is to cause

the implementation to behave in an implementation-defined manner. The behavior might cause translation to fail or cause the translator or the resulting program to behave in a non-conforming manner.

It should be clarified that the extent of the non-conformance is limited to the implementation-defined behavior.




2181. Normative requirements in an informative Annex

Section: Clause Annex B  [implimits]     Status: drafting     Submitter: Sean Hunt     Date: 2015-10-18

According to Clause Annex B [implimits] paragraph 1,

Because computers are finite, C++ implementations are inevitably limited in the size of the programs they can successfully process. Every implementation shall document those limitations where known.

Because Annex Clause Annex B [implimits] is informative, not normative, it should not use “shall.”




1279. Additional differences between C++ 2003 and C++ 2011

Section: C.5  [diff.cpp03]     Status: drafting     Submitter: Nikolay Ivchenkov     Date: 2011-03-27

A number of differences between C++03 and C++11 were omitted from C.5 [diff.cpp03]:

Additional note (January, 2012):

In addition to the items previously mentioned, access declarations were removed from C++11 but are not mentioned in C.5 [diff.cpp03].

Proposed (partial) resolution (February, 2012):

Add the following as a new section in C.5 [diff.cpp03]:

C.2.5 11.8 [class.access]: member access control pdiff.cpp03.class.access

Change: Remove access declarations.

Rationale: Removal of feature deprecated since C++ 1998.

Effect on original feature: Valid C++ 2003 code that uses access declarations is ill-formed in this International Standard. Instead, using-declarations (9.9 [namespace.udecl]) can be used.






Issues with "Open" Status


783. Definition of “argument”

Section: Clause 3  [intro.defs]     Status: open     Submitter: UK     Date: 3 March, 2009

N2800 comment UK 3

The definition of an argument does not seem to cover many assumed use cases, and we believe that is not intentional. There should be answers to questions such as: Are lambda-captures arguments? Are type names in a throw-spec arguments? “Argument” to casts, typeid, alignof, alignas, decltype and sizeof? why in x[arg] arg is not an argument, but the value forwarded to operator[]() is? Does not apply to operators as call-points not bounded by parentheses? Similar for copy initialization and conversion? What are deduced template “arguments?” what are “default arguments?” can attributes have arguments? What about concepts, requires clauses and concept_map instantiations? What about user-defined literals where parens are not used?




2632. 'user-declared' is not defined

Section: Clause 3  [intro.defs]     Status: open     Submitter: Anoop Rana     Date: 2022-09-07

The term "user-declared" is used 30 times throughout the standard, but it is not defined.

Possible resolution:

Add a new entry after 3.66 [defns.unspecified] as follows:

user-declared [defns.user.declared]

declared in a translation unit using a declarator-id

[ Note 1 to entry: In contrast, some special member functions can be implicitly declared by the implementation. -- end note]




949. Requirements for freestanding implementations

Section: 4.1  [intro.compliance]     Status: open     Submitter: Detlef Vollman     Date: 2 August, 2009

According to 4.1 [intro.compliance] paragraph 7,

A freestanding implementation is one in which execution may take place without the benefit of an operating system, and has an implementation-defined set of libraries that includes certain language-support libraries (16.4.2.5 [compliance]).

This definition links two relatively separate topics: the lack of an operating system and the minimal set of libraries. Furthermore, 6.9.3.1 [basic.start.main] paragraph 1 says:

[Note: in a freestanding environment, start-up and termination is implementation-defined; start-up contains the execution of constructors for objects of namespace scope with static storage duration; termination contains the execution of destructors for objects with static storage duration. —end note]

It would be helpful if the two characteristics (lack of an operating system and restricted set of libraries) were named separately and if these statements were clarified to identify exactly what is implementation-defined.

Notes from the October, 2009 meeting:

The CWG felt that it needed a specific proposal in a paper before attempting to resolve this issue.




1698. Files ending in \

Section: 5.2  [lex.phases]     Status: open     Submitter: David Krauss     Date: 2013-06-10

The description of how to handle file not ending in a newline in 5.2 [lex.phases] paragraph 1, phase 2, is:

  1. Each instance of a backslash character (\) immediately followed by a new-line character is deleted, splicing physical source lines to form logical source lines. Only the last backslash on any physical source line shall be eligible for being part of such a splice. If, as a result, a character sequence that matches the syntax of a universal-character-name is produced, the behavior is undefined. A source file that is not empty and that does not end in a new-line character, or that ends in a new-line character immediately preceded by a backslash character before any such splicing takes place, shall be processed as if an additional new-line character were appended to the file.

This is not clear regarding what happens if the last character in the file is a backslash. In such a case, presumably the result of adding the newline should not be a line splice but rather a backslash preprocessing-token (that will be diagnosed as an invalid token in phase 7), but that should be spelled out.




2573. Undefined behavior when splicing results in a universal-character-name

Section: 5.2  [lex.phases]     Status: open     Submitter: US     Date: 2019-10-23     Liaison: SG12

(From National Body comments US 024 and US 025 on the C++20 DIS.)

Subclause 5.2 [lex.phases] bullet 1.2 specifies:

Except for splices reverted in a raw string literal, if a splice results in a character sequence that matches the syntax of a universal-character-name, the behavior is undefined.

Undefined behavior during lexing is not acceptable. The behavior ought to be well-defined, ill-formed, or conditionally-supported.

Additional notes (January, 2023):

Forwarded to SG12 with paper issue 1405, by decision of the CWG and SG12 chairs.




2574. Undefined behavior when lexing unmatched quotes

Section: 5.4  [lex.pptoken]     Status: open     Submitter: US     Date: 2019-10-23     Liaison: SG12

(From National Body comment US 027 on the C++20 DIS.)

Subclause 5.4 [lex.pptoken] paragraph 2 specifies:

If a U+0027 apostrophe or a U+0022 quotation mark character matches the last category, the behavior is undefined.

Undefined behavior during lexing is not acceptable. This ought to be ill-formed.

Additional notes (January, 2023):

Forwarded to SG12 with paper issue 1406, by decision of the CWG and SG12 chairs.




1266. user-defined-integer-literal overflow

Section: 5.13.8  [lex.ext]     Status: open     Submitter: Michael Wong     Date: 2011-03-20

The decimal-literal in a user-defined-integer-literal might be too large for an unsigned long long to represent (in implementations with extended integer types). In such cases, the original intent appears to have been to call a raw literal operator or a literal operator template; however, the existing wording of 5.13.8 [lex.ext] paragraph 3 always calls the unsigned long long literal operator if it exists, regardless of the value of the decimal-literal.




1209. Is a potentially-evaluated expression in a template definition a “use?”

Section: 6.3  [basic.def.odr]     Status: open     Submitter: Johannes Schaub     Date: 2010-10-08

Consider the following complete program:

    void f();
    template<typename T> void g() { f(); }
    int main() { }

Must f() be defined to make this program well-formed? The current wording of 6.3 [basic.def.odr] does not make any special provision for expressions that appear only in uninstantiated template definitions.

(See also issue 1254.)


2488. Overloading virtual functions and functions with trailing requires-clauses

Section: 6.4.1  [basic.scope.scope]     Status: open     Submitter: Jiang An     Date: 2020-08-19

According to 6.4.1 [basic.scope.scope] paragraph 3,

Two declarations correspond if they (re)introduce the same name, both declare constructors, or both declare destructors, unless

This would indicate that a virtual function (which cannot have a trailing requires-clause, per 11.7.3 [class.virtual] paragraph 6) can be overloaded with a non-virtual member function with the same parameter type list but with a trailing requires-clause. However, this is not implementable on some ABIs, since the mangling of the two functions would be the same. For example:


  #include <type_traits>
  template<class T>
  struct Foo {
     virtual void fun() const {}
     void fun() const requires std::is_object_v<T> {}
  };
  int main() {
    Foo<int>{}.fun();
  }

Should such overloading be ill-formed or conditionally-supported, or should the current rules be kept?

Rationale (August, 2021):

CWG felt that the current rules are correct; it simply means that only the virtual function can be called, and all other references are simply ambiguous. (See also issue 2501 for a related question dealing with explicit instantiation.

Notes from the November, 2021 teleconference:

The issue has been reopened in response to additional discussion.

CWG 2022-11-11

This is related to issue 2501. CWG solicits a paper to address this issue.




380. Definition of "ambiguous base class" missing

Section: 6.5.2  [class.member.lookup]     Status: open     Submitter: Jason Merrill     Date: 22 Oct 2002

The term "ambiguous base class" doesn't seem to be actually defined anywhere. 6.5.2 [class.member.lookup] paragraph 7 seems like the place to do it.




2567. Operator lookup ambiguity

Section: 6.5.2  [class.member.lookup]     Status: open     Submitter: Daveed Vandevoorde     Date: 2022-04-01

Consider:

  struct B1 {
    bool operator==(B1 const&) const;
  };
  struct B2 {
    bool operator==(B2 const&) const;
  };
  struct D: B1, B2 {} d;
  bool operator==(D const&, D const&);

  auto r = d == d; // ambiguous?

There is implementation divergence in handling this example; some implementations select the non-member operator, others diagnose an ambiguous lookup.

Member name lookup for operator== is ambiguous, making the program ill-formed per 6.5.2 [class.member.lookup] paragraph 6:

The result of the search is the declaration set of S(N, T). If it is an invalid set, the program is ill-formed.

There is no provision for simply failing if the lookup is invoked as part of some larger lookup, as in the case of a lookup for an overloaded operator (12.2.2.3 [over.match.oper] paragraph 3):

For a unary operator @ with an operand of type cv1 T1, and for a binary operator @ with a left operand of type cv1 T1 and a right operand of type cv2 T2, four sets of candidate functions, designated member candidates, non-member candidates, built-in candidates, and rewritten candidates, are constructed as follows:

It is unclear whether that is intended or desirable.

Suggested resolution:

Change in 6.5.2 [class.member.lookup] paragraph 6 as follows:

The result of the search is If the declaration set of S(N, T). If it is an invalid set, the program is ill-formed the result of the search is an empty set; otherwise, the result is that set.



2670. Programs and translation units

Section: 6.6  [basic.link]     Status: open     Submitter: Gabriel dos Reis     Date: 2022-12-21

Subclause 6.6 [basic.link] paragraph 1 specifies:

A program consists of one or more translation units (5.1 [lex.separate]) linked together. A translation unit consists of a sequence of declarations.

Subclause 5.1 [lex.separate] paragraph 1 defines "translation unit":

A source file together with all the headers (16.4.2.3 [headers]) and source files included (15.3 [cpp.include]) via the preprocessing directive #include, less any source lines skipped by any of the conditional inclusion (15.2 [cpp.cond]) preprocessing directives, is called a translation unit.

Subclause 5.2 [lex.phases] paragraph 7 first mentions the "translation" of translation units:

Whitespace characters separating tokens are no longer significant. Each preprocessing token is converted into a token (5.6 [lex.token]). The resulting tokens are syntactically and semantically analyzed and translated as a translation unit.

However, 5.2 [lex.phases] paragraph 8 introduces "instantiation units" and 5.2 [lex.phases] paragraph 9 specifies:

Translated translation units and instantiation units are combined as follows:

and 5.2 [lex.phases] paragraph 9 specifies:

All external entity references are resolved. Library components are linked to satisfy external references to entities not defined in the current translation. All such translator output is collected into a program image which contains information needed for execution in its execution environment.

The term "linking" in 5.2 [lex.phases] comes after translated translation units and instantiation units are combined, yet 6.6 [basic.link] paragraph 1 states that (untranslated) translation units are linked to form a program. That seems inconsistent.

Additional notes (February, 2023)

See also issue 2518, which introduced the term "preprocessing translation unit" to remove the conflict between 6.6 [basic.link] paragraph 1 and 5.1 [lex.separate] paragraph 1.




2706. Repeated structured binding declarations

Section: 6.6  [basic.link]     Status: open     Submitter: Jim X     Date: 2023-02-13

Consider:

  struct A {
    int a;
  };
  struct B {
    int b;
  };
  auto&& [x] = A{}; //#1
  auto&& [x] = B{}; //#2

A rule is missing to make such repeated structured binding declarations ill-formed.

Suggested resolution:

Change in 6.6 [basic.link] paragraph 11 as follows:

For any two declarations of an entity E:



1953. Data races and common initial sequence

Section: 6.7.1  [intro.memory]     Status: open     Submitter: Faisal Vali     Date: 2014-06-23

According to 6.7.1 [intro.memory] paragraph 3,

A memory location is either an object of scalar type or a maximal sequence of adjacent bit-fields all having non-zero width. [Note: Various features of the language, such as references and virtual functions, might involve additional memory locations that are not accessible to programs but are managed by the implementation. —end note] Two or more threads of execution (6.9.2 [intro.multithread]) can update and access separate memory locations without interfering with each other.

It is not clear how this relates to the permission granted in 11.4 [class.mem] paragraph 18 to inspect the common initial sequence of standard-layout structs that are members of a standard-layout union. If one thread is writing to the common initial sequence and another is reading from it via a different struct, that should constitute a data race, but the current wording does not clearly state that.




2334. Creation of objects by typeid

Section: 6.7.2  [intro.object]     Status: open     Submitter: Chris Hallock     Date: 2017-01-30

The list of ways that an object may be created in 6.7.2 [intro.object] paragraph 1 does not include creation of type_info objects by typeid expressions, but 7.6.1.8 [expr.typeid] does not appear to require that such objects exist before they are referenced. Should the list in 6.7.2 [intro.object] be extended to include this case?




419. Can cast to virtual base class be done on partially-constructed object?

Section: 6.7.3  [basic.life]     Status: open     Submitter: Judy Ward     Date: 2 June 2003

Consider

  extern "C" int printf (const char *,...);

  struct Base { Base();};
  struct Derived: virtual public Base {
     Derived() {;}
  };

  Derived d;
  extern Derived& obj = d;

  int i;

  Base::Base() {
    if ((Base *) &obj) i = 4;
    printf ("i=%d\n", i);
  }

  int main() { return 0; }

11.9.5 [class.cdtor] paragraph 2 makes this valid, but 6.7.3 [basic.life] paragraph 5 implies that it isn't valid.

Steve Adamczyk: A second issue:

  extern "C" int printf(const char *,...);
  struct A                      { virtual ~A(); int x; };
  struct B : public virtual A   { };
  struct C : public B           { C(int); };
  struct D : public C           { D(); };

  int main()                    { D t; printf("passed\n");return 0; }

  A::~A()                       {}
  C::C(int)                     {}
  D::D() : C(this->x)           {}

Core issue 52 almost, but not quite, says that in evaluating "this->x" you do a cast to the virtual base class A, which would be an error according to 11.9.5 [class.cdtor] paragraph 2 because the base class B constructor hasn't started yet. 7.6.1.5 [expr.ref] should be clarified to say that the cast does need to get done.

James Kanze submitted the same issue via comp.std.c++ on 11 July 2003:

Richard Smith: Nonsense. You can use "this" perfectly happily in a constructor, just be careful that (a) you're not using any members that are not fully initialised, and (b) if you're calling virtual functions you know exactly what you're doing.

In practice, and I think in intent, you are right. However, the standard makes some pretty stringent restrictions in 6.7.3 [basic.life]. To start with, it says (in paragraph 1):

The lifetime of an object is a runtime property of the object. The lifetime of an object of type T begins when: The lifetime of an object of type T ends when:
(Emphasis added.) Then when we get down to paragraph 5, it says:

Before the lifetime of an object has started but after the storage which the object will occupy has been allocated [which sounds to me like it would include in the constructor, given the text above] or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any pointer that refers to the storage location where the object will be or was located may be used but only in limited ways. [...] If the object will be or was of a non-POD class type, the program has undefined behavior if:

[...]

I can't find any exceptions for the this pointer.

Note that calling a non-static function in the base class, or even constructing the base class in initializer list, involves an implicit conversion of this to a pointer to the base class. Thus undefined behavior. I'm sure that this wasn't the intent, but it would seem to be what this paragraph is saying.




2258. Storage deallocation during period of destruction

Section: 6.7.3  [basic.life]     Status: open     Submitter: Richard Smith     Date: 2016-04-12

What happens if the storage for an object is deallocated in its period of destruction? Consider:

  struct Base {
    virtual ~Base() {
      ::operator delete(this);
    }
    void operator delete(void*) {}
  };

  struct Derived : Base {};

  int main() {
    delete new Derived;
  } 

This ought to be undefined behavior, but the standard is silent on the matter.

Notes from the December, 2016 teleconference:

The consensus view was that this should be undefined behavior.




2551. "Refers to allocated storage" has no meaning

Section: 6.7.3  [basic.life]     Status: open     Submitter: Andrey Erokhin     Date: 2020-09-07

6.7.3 [basic.life] paragraph 6 specifies:

Before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any pointer that represents the address of the storage location where the object will be or was located may be used but only in limited ways. For an object under construction or destruction, see 11.9.5 [class.cdtor]. Otherwise, such a pointer refers to allocated storage (6.7.5.5.2 [basic.stc.dynamic.allocation]), and using the pointer as if the pointer were of type void* is well-defined.

Similarly, 6.7.3 [basic.life] paragraph 7 specifies:

Similarly, before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any glvalue that refers to the original object may be used but only in limited ways. For an object under construction or destruction, see 11.9.5 [class.cdtor]. Otherwise, such a glvalue refers to allocated storage (6.7.5.5.2 [basic.stc.dynamic.allocation]), and using the properties of the glvalue that do not depend on its value is well-defined.

In either case, it is unclear what "refers to allocated storage" means, beyond the properties ascribed to an object in 6.7.2 [intro.object].

See also issue 1853.

Suggested resolution:

  1. Change in 6.7.3 [basic.life] paragraph 6 as follows:

    For an object under construction or destruction, see 11.9.5 [class.cdtor]. Otherwise, such a pointer refers to allocated storage (6.7.5.5.2 [basic.stc.dynamic.allocation]), and using the such a pointer as if the pointer were of type void* is well-defined.
  2. Change in 6.7.3 [basic.life] paragraph 7 as follows:

    For an object under construction or destruction, see 11.9.5 [class.cdtor]. Otherwise, such a glvalue refers to allocated storage (6.7.5.5.2 [basic.stc.dynamic.allocation]), and using the properties of the such a glvalue that do not depend on its value is well-defined.



2676. Replacing a complete object having base subobjects

Section: 6.7.3  [basic.life]     Status: open     Submitter: Richard Smith     Date: 2022-12-06

Base subobjects cannot be transparently replaced with complete objects, as specified in 6.7.3 [basic.life] bullet 8.4:

An object o1 is transparently replaceable by an object o2 if:

However, that bullet is over-reaching, because it disallows:

  struct A { int n; };
  struct B : A {};
  B b;
  new (&b) B { {5} };  // New A base class does not transparently replace existing A base class due to /8.4.
  int k = b.n;  // UB: member n of A base class is outside its lifetime

See issue 2677 for a suggested resolution.

Additional notes (February, 2023)

Consider this example:

  struct A {
   int n;
   char c;
   // tail padding
  };
  struct B {
   [[no_unique_address]] A a;
   char in_tail_padding[3];
  };

  B b;
  void f() {
   // Transparently replaces old member, potentially overwriting the data in the tail padding!
   new (&b.a) A{};
  }

The suggestions do not address this example.




365. Storage duration and temporaries

Section: 6.7.5  [basic.stc]     Status: open     Submitter: James Kanze     Date: 24 July 2002

There are several problems with 6.7.5 [basic.stc]:

Steve Adamczyk: There may well be an issue here, but one should bear in mind the difference between storage duration and object lifetime. As far as I can see, there is no particular problem with temporaries having automatic or static storage duration, as appropriate. The point of 6.7.7 [class.temporary] is that they have an unusual object lifetime.

Notes from Ocrober 2002 meeting:

It might be desirable to shorten the storage duration of temporaries to allow reuse of them. The as-if rule allows some reuse, but such reuse requires analysis, including noting whether the addresses of such temporaries have been taken.

Notes from the August, 2011 meeting:

The CWG decided that further consideration of this issue would be deferred until someone produces a paper explaining the need for action and proposing specific changes.

See also issue 1634.




1682. Overly-restrictive rules on function templates as allocation functions

Section: 6.7.5.5.2  [basic.stc.dynamic.allocation]     Status: open     Submitter: Jason Merrill     Date: 2009-03-03

Requirements for allocation functions are given in 6.7.5.5.2 [basic.stc.dynamic.allocation] paragraph 1:

An allocation function can be a function template. Such a template shall declare its return type and first parameter as specified above (that is, template parameter types shall not be used in the return type and first parameter type). Template allocation functions shall have two or more parameters.

There are a couple of problems with this description. First, it is instances of function templates that can be allocation functions, not the templates themselves (cf 6.7.5.5.3 [basic.stc.dynamic.deallocation] paragraph 2, which uses the correct terminology regarding deallocation functions).

More importantly, this specification was written before template metaprogramming was understood and hence prevents use of SFINAE on the return type or parameter type to select among function template specializations. (The parallel passage for deallocation functions in 6.7.5.5.3 [basic.stc.dynamic.deallocation] paragraph 2 shares this deficit.)

(See also issue 1628.)




523. Can a one-past-the-end pointer be invalidated by deleting an adjacent object?

Section: 6.7.5.5.3  [basic.stc.dynamic.deallocation]     Status: open     Submitter: comp.std.c++     Date: 8 July 2005

When an object is deleted, 6.7.5.5.3 [basic.stc.dynamic.deallocation] says that the deallocation “[renders] invalid all pointers referring to any part of the deallocated storage.” According to 6.8.4 [basic.compound] paragraph 3, a pointer whose address is one past the end of an array is considered to point to an unrelated object that happens to reside at that address. Does this need to be clarified to specify that the one-past-the-end pointer of an array is not invalidated by deleting the following object? (See also 7.6.2.9 [expr.delete] paragraph 4, which also mentions that the system deallocation function renders a pointer invalid.)




2434. Mandatory copy elision vs non-class objects

Section: 6.7.7  [class.temporary]     Status: open     Submitter: Richard Smith     Date: 2019-09-30

In the following example,

  int f() {
    X x;
    return 4;
  }
  int a = f();

a must be directly initialized in the return statement of f() because the exception permitting temporaries for function arguments and return types in 6.7.7 [class.temporary] paragraph 3 applies only to certain class types:

When an object of class type X is passed to or returned from a function, if X has at least one eligible copy or move constructor (11.4.4 [special]), each such constructor is trivial, and the destructor of X is either trivial or deleted, implementations are permitted to create a temporary object to hold the function parameter or result object. The temporary object is constructed from the function argument or return value, respectively, and the function's parameter or return object is initialized as if by using the eligible trivial constructor to copy the temporary (even if that constructor is inaccessible or would not be selected by overload resolution to perform a copy or move of the object). [Note: This latitude is granted to allow objects of class type to be passed to or returned from functions in registers. —end note]

This requirement is observable, since the destructor of X in the example could inspect the value of a.

The permissions in this paragraph should also apply to all non-class types.




2666. Lifetime extension through static_cast

Section: 6.7.7  [class.temporary]     Status: open     Submitter: Brian Bi     Date: 2022-10-17

Consider:

  struct S { S(int) {} };

  const S& r1 = 0; // #1
  const S& r2 = static_cast<const S&>(S(0)); // #2
  const S& r3 = static_cast<const S&>(0); // #3
  // Are r1, r2, and r3 dangling past this point?

For #1, a temporary object is materialized and the reference binds to the result of the materialization conversion. This results in lifetime extension per 6.7.7 [class.temporary] bullet 6.1.

For #2, 6.7.7 [class.temporary] bullet 6.6 requires the cast to convert "without a user-defined conversion", but S(0) is such.

For #3, a user-defined conversion is clearly required.

However, implementations treat all three cases the same and do extend the lifetime of the temporary.

Possible resolution:

  1. Change in 7.6.1.9 [expr.static.cast] paragraph 1 as follows:

    The result of the expression static_cast<T>(v) is the result of converting the expression v to type T. If T is an lvalue reference type or an rvalue reference to function type, the result is an lvalue; if T is an rvalue reference to object type, the result is an xvalue; otherwise, the result is a prvalue. If T is a reference type, the operand shall be a glvalue; otherwise, the operand shall be a prvalue. The static_cast operator shall not cast away constness (7.6.1.11 [expr.const.cast]).
  2. Change in 7.6.1.9 [expr.static.cast] paragraph 8 as follows:

    The lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), and function-to-pointer (7.3.4 [conv.func]) conversions are applied to the operand. Such a static_cast is subject to the restriction that the explicit conversion does not cast away constness (7.6.1.11 [expr.const.cast]), and the following additional rules for specific cases:
  3. Change in 7.6.1.10 [expr.reinterpret.cast] paragraph 1 as follows:

    The result of the expression reinterpret_cast<T>(v) is the result of converting the expression v to type T. If T is an lvalue reference type or an rvalue reference to function type, the result is an lvalue; if T is an rvalue reference to object type, the result is an xvalue; otherwise, the result is a prvalue and the lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), and function-to-pointer (7.3.4 [conv.func]) standard conversions are performed on the expression v. If T is a reference type, the operand shall be a glvalue; otherwise, the operand shall be a prvalue. Conversions that can be performed explicitly using reinterpret_cast are listed below. No other conversion can be performed explicitly using reinterpret_cast.
  4. Change in 7.6.1.11 [expr.const.cast] paragraph 1 as follows:

    The result of the expression const_cast<T>(v) is of type T. If T is an lvalue reference to object type, the result is an lvalue; if T is an rvalue reference to object type, the result is an xvalue; otherwise, the result is a prvalue and the lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), and function-to-pointer (7.3.4 [conv.func]) standard conversions are performed on the expression v. If T is a reference type, the operand shall be a glvalue; otherwise, the operand shall be a prvalue. Conversions that can be performed explicitly using const_cast are listed below. No other conversion shall be performed explicitly using const_cast.



350. signed char underlying representation for objects

Section: 6.8  [basic.types]     Status: open     Submitter: Noah Stein     Date: 16 April 2002     Liaison: WG14

Sent in by David Abrahams:

Yes, and to add to this tangent, 6.8.2 [basic.fundamental] paragraph 1 states "Plain char, signed char, and unsigned char are three distinct types." Strangely, 6.8 [basic.types] paragraph 2 talks about how "... the underlying bytes making up the object can be copied into an array of char or unsigned char. If the content of the array of char or unsigned char is copied back into the object, the object shall subsequently hold its original value." I guess there's no requirement that this copying work properly with signed chars!

Notes from October 2002 meeting:

We should do whatever C99 does. 6.5p6 of the C99 standard says "array of character type", and "character type" includes signed char (6.2.5p15), and 6.5p7 says "character type". But see also 6.2.6.1p4, which mentions (only) an array of unsigned char.

Proposed resolution (April 2003):

Change 6.7.3 [basic.life] bullet 5.3 from

to

Change 6.7.3 [basic.life] bullet 6.3 from

to

Change the beginning of 6.8 [basic.types] paragraph 2 from

For any object (other than a base-class subobject) of POD type T, whether or not the object holds a valid value of type T, the underlying bytes (6.7.1 [intro.memory]) making up the object can be copied into an array of char or unsigned char.

to

For any object (other than a base-class subobject) of POD type T, whether or not the object holds a valid value of type T, the underlying bytes (6.7.1 [intro.memory]) making up the object can be copied into an array of byte-character type.

Add the indicated text to 6.8.2 [basic.fundamental] paragraph 1:

Objects declared as characters (char) shall be large enough to store any member of the implementation's basic character set. If a character from this set is stored in a character object, the integral value of that character object is equal to the value of the single character literal form of that character. It is implementation-defined whether a char object can hold negative values. Characters can be explicitly declared unsigned or signed. Plain char, signed char, and unsigned char are three distinct types, called the byte-character types. A char, a signed char, and an unsigned char occupy the same amount of storage and have the same alignment requirements (6.8 [basic.types]); that is, they have the same object representation. For byte-character types, all bits of the object representation participate in the value representation. For unsigned byte-character types, all possible bit patterns of the value representation represent numbers. These requirements do not hold for other types. In any particular implementation, a plain char object can take on either the same values as a signed char or an unsigned char; which one is implementation-defined.

Change 7.2.1 [basic.lval] paragraph 15 last bullet from

to

Notes from October 2003 meeting:

It appears that in C99 signed char may have padding bits but no trap representation, whereas in C++ signed char has no padding bits but may have -0. A memcpy in C++ would have to copy the array preserving the actual representation and not just the value.

March 2004: The liaisons to the C committee have been asked to tell us whether this change would introduce any unnecessary incompatibilities with C.

Notes from October 2004 meeting:

The C99 Standard appears to be inconsistent in its requirements. For example, 6.2.6.1 paragraph 4 says:

The value may be copied into an object of type unsigned char [n] (e.g., by memcpy); the resulting set of bytes is called the object representation of the value.

On the other hand, 6.2 paragraph 6 says,

If a value is copied into an object having no declared type using memcpy or memmove, or is copied as an array of character type, then the effective type of the modified object for that access and for subsequent accesses that do not modify the value is the effective type of the object from which the value is copied, if it has one.

Mike Miller will investigate further.

Proposed resolution (February, 2010):

  1. Change 6.7.3 [basic.life] bullet 5.4 as follows:

  2. ...The program has undefined behavior if:

  3. Change 6.7.3 [basic.life] bullet 6.4 as follows:

  4. ...The program has undefined behavior if:

  5. Change 6.8 [basic.types] paragraph 2 as follows:

  6. For any object (other than a base-class subobject) of trivially copyable type T, whether or not the object holds a valid value of type T, the underlying bytes (6.7.1 [intro.memory]) making up the object can be copied into an array of char or unsigned char a byte-character type (6.8.2 [basic.fundamental]).39 If the content of the that array of char or unsigned char is copied back into the object, the object shall subsequently hold its original value. [Example:...
  7. Change 6.8.2 [basic.fundamental] paragraph 1 as follows:

  8. ...Characters can be explicitly declared unsigned or signed. Plain char, signed char, and unsigned char are three distinct types, called the byte-character types. A char, a signed char, and an unsigned char occupy the same amount of storage and have the same alignment requirements (6.7.6 [basic.align]); that is, they have the same object representation. For byte-character types, all bits of the object representation participate in the value representation. For unsigned character types unsigned char, all possible bit patterns of the value representation represent numbers...
  9. Change 7.2.1 [basic.lval] paragraph 15 final bullet as follows:

  10. If a program attempts to access the stored value of an object through an lvalue of other than one of the following types the behavior is undefined 52

  11. Change 6.7.6 [basic.align] paragraph 6 as follows:

  12. The alignment requirement of a complete type can be queried using an alignof expression (7.6.2.6 [expr.alignof]). Furthermore, the byte-character types (6.8.2 [basic.fundamental]) char, signed char, and unsigned char shall have the weakest alignment requirement. [Note: this enables the byte-character types to be used as the underlying type for an aligned memory area (9.12.2 [dcl.align]). —end note]
  13. Change 7.6.2.8 [expr.new] paragraph 10 as follows:

  14. ...For arrays of char and unsigned char a byte-character type (6.8.2 [basic.fundamental]), the difference between the result of the new-expression and the address returned by the allocation function shall be an integral multiple of the strictest fundamental alignment requirement (6.7.6 [basic.align]) of any object type whose size is no greater than the size of the array being created. [Note: Because allocation functions are assumed to return pointers to storage that is appropriately aligned for objects of any type with fundamental alignment, this constraint on array allocation overhead permits the common idiom of allocating byte-character arrays into which objects of other types will later be placed. —end note]

Notes from the March, 2010 meeting:

The CWG was not convinced that there was a need to change the existing specification at this time. Some were concerned that there might be implementation difficulties with giving signed char the requisite semantics; implementations for which that is true can currently make char equivalent to unsigned char and avoid those problems, but the suggested change would undermine that strategy.

Additional note, November, 2014:

There is now the term “narrow character type” that should be used instead of “byte-character type”.




146. Floating-point zero

Section: 6.8.2  [basic.fundamental]     Status: open     Submitter: Andy Sawyer     Date: 23 Jul 1999

6.8.2 [basic.fundamental] does not impose a requirement on the floating point types that there be an exact representation of the value zero. This omission is significant in 7.3.14 [conv.fctptr] paragraph 1, in which any non-zero value converts to the bool value true.

Suggested resolution: require that all floating point types have an exact representation of the value zero.




251. How many signed integer types are there?

Section: 6.8.2  [basic.fundamental]     Status: open     Submitter: Beman Dawes     Date: 18 Oct 2000

6.8.2 [basic.fundamental] paragraph 2 says that

There are four signed integer types: "signed char", "short int", "int", and "long int."

This would indicate that const int is not a signed integer type.

Notes from the June, 2016 meeting:

See issue 2185.




2689. Are cv-qualified std::nullptr_t fundamental types?

Section: 6.8.2  [basic.fundamental]     Status: open     Submitter: Anoop Rana     Date: 2022-12-08

It is unclear whether cv std::nullptr_t is a fundamental type, given that it is declared in a library header and cv-qualifications are not mentioned in 6.8.2 [basic.fundamental] paragraph 15.

Suggested resolution:

Change in 6.8.2 [basic.fundamental] paragraph 15 as follows:

A type denoted by cv std::nullptr_t is a distinct type. A value of type std::nullptr_t is a null pointer constant (7.3.12 [conv.ptr]). Such values participate in the pointer and the pointer-to-member conversions (7.3.12 [conv.ptr], 7.3.13 [conv.mem]). sizeof(std::nullptr_t) shall be equal to sizeof(void*).

The types described in this subclause are called fundamental types. [Note 11: Even if the implementation defines two or more fundamental types to have the same value representation, they are nevertheless different types. —end note]




2544. Address of past-the-end of a potentially-overlapping subobject

Section: 6.8.4  [basic.compound]     Status: open     Submitter: Jiang An     Date: 2022-02-20

6.8.4 [basic.compound] paragraph 3 states:

A value of a pointer type that is a pointer to or past the end of an object represents the address of the first byte in memory (6.7.1 [intro.memory]) occupied by the object [ Footnote: ... ] or the first byte in memory after the end of the storage occupied by the object, respectively.

A potentially-overlapping subobject of type T may occupy fewer bytes than indicated by sizeof(T), yet pointer arithmetic will only consider sizeof(T), not the number of actually occupied bytes. For example,

struct X {
  X() = default;
  int x;
  short y;
};

struct S {
  [[no_unique_address]] X x;
  short z;
};

static_assert(sizeof(X) == sizeof(S));

On a popular implementation, z is actually put into the tail padding of x, and thus &S().x + 1 does not actually point to "the first byte in memory after the end of the storage occupied by" x.

Suggested resolution (amended 2022-03-10):

Change in 6.8.4 [basic.compound] paragraph 3 as follows:

A value V of a pointer type that is a pointer to or past the end of an object represents the address of the first byte in memory (6.7.1 [intro.memory]) occupied by the object A as follows:




698. The definition of “sequenced before” is too narrow

Section: 6.9.1  [intro.execution]     Status: open     Submitter: Jens Maurer     Date: 13 July, 2008

According to 6.9.1 [intro.execution] paragraph 14, “sequenced before” is a relation between “evaluations.” However, 6.9.3.3 [basic.start.dynamic] paragraph 3 says,

If the completion of the initialization of a non-local object with static storage duration is sequenced before a call to std::atexit (see <cstdlib>, 17.5 [support.start.term]), the call to the function passed to std::atexit is sequenced before the call to the destructor for the object. If a call to std::atexit is sequenced before the completion of the initialization of a non-local object with static storage duration, the call to the destructor for the object is sequenced before the call to the function passed to std::atexit. If a call to std::atexit is sequenced before another call to std::atexit, the call to the function passed to the second std::atexit call is sequenced before the call to the function passed to the first std::atexit call.

Except for the calls to std::atexit, these events do not correspond to “evaluation” of expressions that appear in the program. If the “sequenced before” relation is to be applied to them, a more comprehensive definition is needed.




1842. Unevaluated operands and “carries a dependency”

Section: 6.9.2  [intro.multithread]     Status: open     Submitter: Hans Boehm     Date: 2014-01-23     Liaison: SG1

According to 6.9.2 [intro.multithread] paragraph 9,

An evaluation A carries a dependency to an evaluation B if

The intent is that this does not apply to the second operands of such operators if the first operand is such that they are not evaluated, but the wording is not clear to that effect. (A similar question applies to the non-selected operand of the conditional operator ?:.)

Notes from the October, 2015 meeting:

It appears likely that the text involved will be removed by a revision to the memory_order_consume specification.

Notes from the February, 2016 meeting:

Action on this issue will be deferred until the specification for memory_order_consume is complete; it should not currently be used.




2297. Unclear specification of atomic operations

Section: 6.9.2.2  [intro.races]     Status: open     Submitter: Kazutoshi Satoda     Date: 2016-01-21

It is not sufficiently clear that the only atomic operations are the ones defined in 33.5 [atomics] by the library. The intent is that no accesses are atomic unless the Standard describes them as such.

An additional problem is that, e.g., new and delete are defined to be synchronization operations, but they are not defined in Clauses 33.5 [atomics] and Clause 33 [thread].

Suggested resolution:

Change 6.9.2.2 [intro.races] paragraph 3 as follows:

The library defines a number the set of atomic operations (33.5 [atomics]) and operations on mutexes (Clause 33 [thread]) that. Some of these, and some other library operations, such as those on mutexes ( Clause 33 [thread]) are specially identified as synchronization operations. These operations...

Notes from the April, 2017 teleconference:

CWG determined that this issue should be handled editorially; it will be in "review" status until the change has been made and verified. See editorial issue 1611.

Additional notes, October, 2018:

This is also library issue 2506. SG1 has requested a paper to deal with this issue, so it is no longer considered editorial.




2298. Actions and expression evaluation

Section: 6.9.2.2  [intro.races]     Status: open     Submitter: Kazutoshi Satoda     Date: 2016-01-21     Liaison: SG1

Section 6.9.2.2 [intro.races] uses the terms “action” and “expression evaluation” interchangeably. “Sequenced before” is defined on expression evaluations. Probably none of those is correct.

We should really be talking about individual accesses to “memory locations”. Talking about larger “expression evaluations” is incorrect, since they may include internal synchronization. Thus concurrent evaluation of large conflicting expression evaluations may not actually correspond to a data race. I'm not sure what term we should be using instead of “expression evaluation” to denote such individual accesses. Call it X for now.

There is also an issue with the fact that “sequenced before” is defined on expression evaluation. “Sequenced before” should also be defined on Xs. It doesn't make any sense to talk about “sequenced before” ordering on two evaluations when one includes the other. Whenever we say “A is sequenced before B”, we probably really mean that all Xs in A are sequenced before all Xs in B. We could probably just include a blanket statement to that effect.

Additional notes (April, 2022)

Forwarded to SG1 with paper issue 1234, reflecting the former "concurrency" status of this issue.




2587. Visible side effects and initial value of an object

Section: 6.9.2.2  [intro.races]     Status: open     Submitter: Andrey Erokhin     Date: 2022-05-10

Subclause 6.9.2.2 [intro.races] paragraph 13 specifies:

A visible side effect A on a scalar object or bit-field M with respect to a value computation B of M satisfies the conditions: The value of a non-atomic scalar object or bit-field M, as determined by evaluation B, shall be the value stored by the visible side effect A.

However, a side effect is defined as 6.9.1 [intro.execution] paragraph 7:

Reading an object designated by a volatile glvalue (7.2.1 [basic.lval]), modifying an object, calling a library I/O function, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment.

It seems that initialization of an object is not a side effect, and thus the value of an scalar object can never be the value obtained during initialization.




371. Interleaving of constructor calls

Section: 6.9.3.2  [basic.start.static]     Status: open     Submitter: Matt Austern     Date: 7 August 2002

Is a compiler allowed to interleave constructor calls when performing dynamic initialization of nonlocal objects? What I mean by interleaving is: beginning to execute a particular constructor, then going off and doing something else, then going back to the original constructor. I can't find anything explicit about this in 6.9.3.2 [basic.start.static].

I'll present a few different examples, some of which get a bit wild. But a lot of what this comes down to is exactly what the standard means when it talks about the order of initialization. If it says that some object x must be initialized before a particular event takes place, does that mean that x's constructor must be entered before that event, or does it mean that it must be exited before that event? If object x must be initialized before object y, does that mean that x's constructor must exit before y's constructor is entered?

(The answer to that question might just be common sense, but I couldn't find an answer in 6.9.3.2 [basic.start.static]. Actually, when I read 6.9.3.2 [basic.start.static] carefully, I find there are a lot of things I took for granted that aren't there.)

OK, so a few specific scenerios.

  1. We have a translation unit with nonlocal objects A and B, both of which require dynamic initialization. A comes before B. A must be initialized before B. May the compiler start to construct A, get partway through the constructor, then construct B, and then go back to finishing A?
  2. We have a translation unit with nonlocal object A and function f. Construction of A is deferred until after the first statement of main. A must be constructed before the first use of f. Is the compiler permitted to start constructing A, then execute f, then go back to constructing A?
  3. We have nonlocal objects A and B, in two different translation units. The order in which A and B are constructed is unspecified by the Standard. Is the compiler permitted to begin constructing A, then construct B, then finish A's constructor? Note the implications of a 'yes' answer. If A's and B's constructor both call some function f, then the call stack might look like this:
       <runtime gunk>
         <Enter A's constructor>
            <Enter f>
               <runtime gunk>
                  <Enter B's constructor>
                     <Enter f>
                     <Leave f>
                  <Leave B's constructor>
            <Leave f>
         <Leave A's constructor>
    
    The implication of a 'yes' answer for users is that any function called by a constructor, directly or indirectly, must be reentrant.
  4. This last example is to show why a 'no' answer to #3 might be a problem too. New scenerio: we've got one translation unit containing a nonlocal object A and a function f1, and another translation unit containing a nonlocal object B and a function f2. A's constructor calls f2. Initialization of A and B is deferred until after the first statement of main(). Someone in main calls f1. Question: is the compiler permitted to start constructing A, then go off and construct B at some point before f2 gets called, then go back and finish constructing A? In fact, is the compiler required to do that? We've got an unpleasant tension here between the bad implications of a 'yes' answer to #3, and the explicit requirement in 6.9.3.2 [basic.start.static] paragraph 3.

At this point, you might be thinking we could avoid all of this nonsense by removing compilers' freedom to defer initialization until after the beginning of main(). I'd resist that, for two reasons. First, it would be a huge change to make after the standard has been out. Second, that freedom is necessary if we want to have support for dynamic libraries. I realize we don't yet say anything about dynamic libraries, but I'd hate to make decisions that would make such support even harder.




1294. Side effects in dynamic/static initialization

Section: 6.9.3.2  [basic.start.static]     Status: open     Submitter: Daniel Krügler     Date: 2011-04-08

According to 6.9.3.2 [basic.start.static] paragraph 3,

An implementation is permitted to perform the initialization of a non-local variable with static storage duration as a static initialization even if such initialization is not required to be done statically, provided that

This does not consider side effects of the initialization in this determination, only the values of namespace-scope variables.

CWG 2022-11-11

The precise normative identification of side effects relevant for the rule remains open. An approach similar to the constexpr model of considering the transitive hull of evaluations might be applicable.




1659. Initialization order of thread_local template static data members

Section: 6.9.3.2  [basic.start.static]     Status: open     Submitter: Richard Smith     Date: 2013-04-14

According to 6.9.3.2 [basic.start.static] paragraph 5,

It is implementation-defined whether the dynamic initialization of a non-local variable with static or thread storage duration is done before the first statement of the initial function of the thread. If the initialization is deferred to some point in time after the first statement of the initial function of the thread, it shall occur before the first odr-use (6.3 [basic.def.odr]) of any variable with thread storage duration defined in the same translation unit as the variable to be initialized.

This doesn't consider that initialization of instantiations of static data members of class templates (which can be thread_local) are unordered. Presumably odr-use of such a static data member should not trigger the initialization of any thread_local variable other than that one?




640. Accessing destroyed local objects of static storage duration

Section: 6.9.3.3  [basic.start.dynamic]     Status: open     Submitter: Howard Hinnant     Date: 30 July 2007

6.9.3.3 [basic.start.dynamic] paragraph 2 says,

If a function contains a local object of static storage duration that has been destroyed and the function is called during the destruction of an object with static storage duration, the program has undefined behavior if the flow of control passes through the definition of the previously destroyed local object.

I would like to turn this behavior from undefined to well-defined behavior for the purpose of achieving a graceful shutdown, especially in a multi-threaded world.

Background: Alexandrescu describes the “phoenix singleton” in Modern C++ Design. This is a class used as a function local static, that will reconstruct itself, and reapply itself to the atexit chain, if the program attempts to use it after it is destructed in the atexit chain. It achieves this by setting a “destructed flag” in its own state in its destructor. If the object is later accessed (and a member function is called on it), the member function notes the state of the “destructed flag” and does the reconstruction dance. The phoenix singleton pattern was designed to address issues only in single-threaded code where accesses among static objects can have a non-scoped pattern. When we throw in multi-threading, and the possibility that threads can be running after main returns, the chances of accessing a destroyed static significantly increase.

The very least that I would like to see happen is to standardize what I believe is existing practice: When an object is destroyed in the atexit chain, the memory the object occupied is left in whatever state the destructor put it in. If this can only be reliably done for objects with standard layout, that would be an acceptable compromise. This would allow objects to set “I'm destructed” flags in their state and then do something well-defined if accessed, such as throw an exception.

A possible refinement of this idea is to have the compiler set up a 3-state flag around function-local statics instead of the current 2-state flag:

We have the first two states today. We might choose to add the third state, and if execution passes over a function-local static with “destroyed” state, an exception could be thrown. This would mean that we would not have to guarantee memory stability in destroyed objects of static duration.

This refinement would break phoenix singletons, and is not required for the ~mutex()/~condition() I've described and prototyped. But it might make it easier for Joe Coder to apply this kind of guarantee to his own types.




2684. thread_local dynamic initialization

Section: 6.9.3.3  [basic.start.dynamic]     Status: open     Submitter: Jason Merrill     Date: 2023-01-06

Subclause 6.9.3.3 [basic.start.dynamic] paragraph 7 specifies:

It is implementation-defined whether the dynamic initialization of a non-block non-inline variable with thread storage duration is sequenced before the first statement of the initial function of a thread or is deferred. If it is deferred, the initialization associated with the entity for thread t is sequenced before the first non-initialization odr-use by t of any non-inline variable with thread storage duration defined in the same translation unit as the variable to be initialized. ...

How does the rule quoted above affect variables declared constinit? For example:

  extern thread_local constinit int x;

Clang and gcc do not emit suitable wrapper code to allow for deferred initialization of x, which is non-conforming. Should this be allowed?

Suggested resolution:

Change in 6.9.3.3 [basic.start.dynamic] paragraph 7 as follows:

It is implementation-defined whether the dynamic initialization of a non-block non-inline variable with thread storage duration is sequenced before the first statement of the initial function of a thread or is deferred. If it is deferred, the initialization associated with the entity for thread t is sequenced before the first non-initialization odr-use by t of any non-inline variable with thread storage duration and dynamic initialization defined in the same translation unit as the variable to be initialized. ...



2438. Problems in the specification of qualification conversions

Section: 7.3.6  [conv.qual]     Status: open     Submitter: Richard Smith     Date: 2019-08-14
  1. A type has multiple cv-decompositions, and 7.3.6 [conv.qual] paragraph 3 does not say which one to use when determining the cv-combined type. Should this be the longest decomposition that works, i.e., the greatest n for which you can decompose both types? (We used to refer to the cv-qualification signature, which implicitly meant to take the longest decomposition.)

  2. When computing the cv-combined types of two types T1 and T2, if U1 and U2 are different, shouldn't we add const to all layers above that in the type?

  3. cv03 is left unspecified by the wording in paragraph 3.

  4. We are too eager to replace a Pi3 with “array of unknown bound of”. That should only happen if both Pi1 and Pi2 are array types, or we end up not forming a type T3 that is similar to T1. For example, the cv-combined type of int** and const int (*)[], when decomposed with n == 2, is required to be const int (*)[] by the bulleted rules, and that type is not similar to the original T1.

  5. In various places, we have operators that say, “if one operand is of pointer type, apply array-to-pointer conversions, pointer conversions, and qualification conversions to bring the two operands to their composite pointer type,” but that doesn't work, because the definition of composite pointer type can't cope with one operand being a pointer and the other being an array. We either need to define the composite pointer type of a pointer and an array (and, if that's done in terms of computing the cv-combined type, be careful to ensure that computing the cv-combined type actually works in that case) or to perform the array-to-pointer conversion before considering the composite pointer type.




2549. Implicitly moving the operand of a throw-expression in unevaluated contexts

Section: 7.5.4.3  [expr.prim.id.qual]     Status: open     Submitter: Richard Smith     Date: 2022-03-11

Consider:

  void f() {
    X x;
    // Is x an lvalue or an xvalue here?
    void g(int n = (decltype((throw x, 0))()));  // status quo: x is move-eligible here
  }

  void f() {
    X x;
    struct A {
      void g() {
        try {
          struct Y {
            // Is x an lvalue or an xvalue here?
            void h(int n = (decltype((throw x, 0))()));
          };
        } catch (...) { }
      }
    };
  }

11.9.6 [class.copy.elision] paragraph 3 specifies:

An implicitly movable entity is a variable of automatic storage duration that is either a non-volatile object or an rvalue reference to a non-volatile object type. In the following copy-initialization contexts, a move operation is first considered before attempting a copy operation:

Thus, in the first example above, x is treated as an xvalue, but it is treated as an lvalue in the second example. This outcome is surprising.

(P2266R2 (Simpler implicit move) moves this wording, introduced by P1825R0 (Merged wording for P0527R1 and P1155R3), from 11.9.6 [class.copy.elision] to 7.5.4.3 [expr.prim.id.qual].)

Suggested resolution (post-P2266R3):

Change in 7.5.4.3 [expr.prim.id.qual] paragraph 3:

An implicitly movable entity is a variable of with automatic storage duration that is either a non-volatile object or an rvalue reference to a non-volatile object type. In the following contexts, an An id-expression is move-eligible: if



2561. Conversion to function pointer for lambda with explicit object parameter

Section: 7.5.5.2  [expr.prim.lambda.closure]     Status: open     Submitter: Barry Revzin     Date: 2022-02-14

P0847R7 (Deducing this) (approved October, 2021) added explicit-object member functions. Consider:

  struct C {
    C(auto) { }
  };

  void foo() {
    auto l = [](this C) { return 1; };
    void (*fp)(C) = l;
    fp(1); // same effect as decltype(l){}() or decltype(l){}(1) ?
  }

Subclause 7.5.5.2 [expr.prim.lambda.closure] paragraph 8 does not address explicit object member functions:

The closure type for a non-generic lambda-expression with no lambda-capture whose constraints (if any) are satisfied has a conversion function to pointer to function with C++ language linkage (9.11 [dcl.link]) having the same parameter and return types as the closure type's function call operator. The conversion is to “pointer to noexcept function” if the function call operator has a non-throwing exception specification. The value returned by this conversion function is the address of a function F that, when invoked, has the same effect as invoking the closure type's function call operator on a default-constructed instance of the closure type. F is a constexpr function if...

Suggested resolution:

  1. Change in 7.5.5.2 [expr.prim.lambda.closure] paragraph 8 as follows:

    ... The value returned by this conversion function is
    • for a lambda-expression whose parameter-declaration-clause has an explicit object parameter, the address of the function call operator (7.6.2.2 [expr.unary.op];
    • otherwise, the address of a function F that, when invoked, has the same effect as invoking the closure type's function call operator on a default-constructed instance of the closure type.
    F is a constexpr function if... is an immediate function.

    [ Example:

      struct C {
        C(auto) { }
      };
    
      void foo() {
        auto a = [](C) { return 0; };
        int (*fp)(C) = a;   // OK
        fp(1);              // same effect as decltype(a){}(1)
        auto b = [](this C) { return 1; };
        fp = b;             // OK
        fp(1);              // same effect as (&decltype(b)::operator())(1)
      }
    

    -- end example ]

  2. Change in 7.5.5.2 [expr.prim.lambda.closure] paragraph 11 as follows:

    The value returned by any given specialization of this conversion function template is
    • for a lambda-expression whose parameter-declaration-clause has an explicit object parameter, the address of the corresponding function call operator template specialization (7.6.2.2 [expr.unary.op]);
    • otherwise, the address of a function F that, when invoked, has the same effect as invoking the generic lambda's corresponding function call operator template specialization on a default-constructed instance of the closure type.
    F is a constexpr function if...



2560. Parameter type determination in a requirement-parameter-list

Section: 7.5.7.1  [expr.prim.req.general]     Status: open     Submitter: Daveed Vandevoorde     Date: 2020-01-21

Consider:

  template<typename T>
    requires requires (T p[10]) { (decltype(p))nullptr; }
  int v = 42;
  auto r = v<int>; // well-formed? 

This example is only well-formed if the type of the parameter p is adjusted to T*, but the provisions in 9.3.4.6 [dcl.fct] paragraph 5 cover function parameters only.

One option is to specify application of the same adjustments as for function parameters. Another option is to specify rules that arguably are more useful in a requires-expression.

Suggested resolution:

Change in 7.5.7.1 [expr.prim.req.general] paragraph 3 as follows:

A requires-expression may introduce local parameters using a parameter-declaration-clause (9.3.4.6 [dcl.fct]). A local parameter of a requires-expression shall not have a default argument. The type of such a parameter is determined as specified for a function parameter in 9.3.4.6 [dcl.fct]. These parameters have no linkage, storage, or lifetime; they are only used as notation for the purpose of defining requirements. The parameter-declaration-clause of a requirement-parameter-list shall not terminate with an ellipsis.
[Example 2:
  template<typename T>
  concept C = requires(T t, ...) {  // error: terminates with an ellipsis
    t;
  };
  concept C2 = requires(T p[2]) {
    (decltype(p))nullptr;           // OK, p has type "pointer to T"
  };
end example]



2565. Invalid types in the parameter-declaration-clause of a requires-expression

Section: 7.5.7.1  [expr.prim.req.general]     Status: open     Submitter: Barry Revzin     Date: 2022-04-07

Consider:

  template <typename T>
  concept C = requires (typename T::type x) {
    x + 1;
  };

  static_assert(!C<int>);

All implementations accept this translation unit. However, the rule in 7.5.7.1 [expr.prim.req.general] paragraph 5 does not cover the parameter-declaration-clause::

The substitution of template arguments into a requires-expression may result in the formation of invalid types or expressions in its requirements or the violation of the semantic constraints of those requirements. In such cases, the requires-expression evaluates to false; it does not cause the program to be ill-formed. The substitution and semantic constraint checking proceeds in lexical order and stops when a condition that determines the result of the requires-expression is encountered. If substitution (if any) and semantic constraint checking succeed, the requires-expression evaluates to true.

Suggested resolution:

Change in 7.5.7.1 [expr.prim.req.general] paragraph 5:

The substitution of template arguments into a requires-expression may result in the formation of invalid types or expressions in its parameter-declaration-clause (if any) or its requirements or the violation of the semantic constraints of those requirements. In such cases, ...



1642. Missing requirements for prvalue operands

Section: 7.6  [expr.compound]     Status: open     Submitter: Joseph Mansfield     Date: 2013-03-15

Although the note in 7.2.1 [basic.lval] paragraph 1 states that

The discussion of each built-in operator in Clause 7 [expr] indicates the category of the value it yields and the value categories of the operands it expects

in fact, many of the operators that take prvalue operands do not make that requirement explicit. Possible approaches to address this failure could be a blanket statement that an operand whose value category is not stated is assumed to be a prvalue; adding prvalue requirements to each operand description for which it is missing; or changing the description of the usual arithmetic conversions to state that they imply the lvalue-to-rvalue conversion, which would cover the majority of the omissions.

(See also issue 1685, which deals with an inaccurately-specified value category.)




2284. Sequencing of braced-init-list arguments

Section: 7.6.1.3  [expr.call]     Status: open     Submitter: Richard Smith     Date: 2016-06-30

As of P0400R0 (Wording for Order of Evaluation of Function Arguments), we have in subclause 7.6.1.3 [expr.call] paragraph 8:

The postfix-expression is sequenced before each expression in the expression-list and any default argument. The initialization of a parameter, including every associated value computation and side effect, is indeterminately sequenced with respect to that of any other parameter.

What about the case when the element in the expression-list is a braced-init-list rather than an expression? The braced-init-list is certainly evaluated left-to-right, but is that required to happen after we evaluate the postfix-expression?




2515. Result of a function call

Section: 7.6.1.3  [expr.call]     Status: open     Submitter: Andrey Erokhin     Date: 2021-11-06

The editorial change referred to in the resolution of issue 2495 updated the terminology used to describe the return statement to allow for the fact that the operand could be a braced-init-list instead of an expression. A similar problem exists describing the result of a function call in 7.6.1.3 [expr.call] paragraph 9:

The result of a function call is the result of the possibly-converted operand of the return statement (8.7.4 [stmt.return]) that transferred control out of the called function

It's incorrect to refer to “converting” the operand when it is a braced-init-list.




2660. Confusing term "this parameter"

Section: 7.6.1.3  [expr.call]     Status: open     Submitter: Anoop Rana     Date: 2022-11-23

Subclause 7.6.1.3 [expr.call] paragraph 7 specifies:

If the function is an implicit object member function, the this parameter of the function (7.5.2 [expr.prim.this]) is initialized with a pointer to the object of the call, converted as if by an explicit type conversion (7.6.3 [expr.cast]).

The term "this parameter" is undefined.

Suggested resolution:

Change in 7.6.1.3 [expr.call] paragraph 7 as follows:

If the function is an implicit object member function, the this implicit object parameter of the function (7.5.2 [expr.prim.this]) is initialized with a pointer to the object of the call, converted as if by an explicit type conversion (7.6.3 [expr.cast]).



2688. Calling explicit object member functions

Section: 7.6.1.3  [expr.call]     Status: open     Submitter: Matthew House     Date: 2023-01-16

Subclause 7.6.1.3 [expr.call] paragraphs 1 and 2 do not properly handle explicit object member functions. Such a function can be called via a function pointer, via an lvalue obtained by dereferencing a function pointer, and via a class member access expression.




914. Value-initialization of array types

Section: 7.6.1.4  [expr.type.conv]     Status: open     Submitter: Gabriel Dos Reis     Date: 10 June, 2009     Liaison: EWG

Although value-initialization is defined for array types and the () initializer is permitted in a mem-initializer naming an array member of a class, the syntax T() (where is an array type) is explicitly forbidden by 7.6.1.4 [expr.type.conv] paragraph 2. This is inconsistent and the syntax should be permitted.

Rationale (July, 2009):

The CWG was not convinced of the utility of this extension, especially in light of questions about handling the lifetime of temporary arrays. This suggestion needs a proposal and analysis by the EWG before it can be considered by the CWG.

EWG 2022-11-11

This is a defect; a paper is needed. This is tracked in github issue cplusplus/papers#1372.




2705. Accessing ambiguous subobjects

Section: 7.6.1.5  [expr.ref]     Status: open     Submitter: Davis Herring     Date: 2023-02-11

Consider:

  struct A {int i;};
  struct X : A {};
  struct Y : A {};
  struct S : X,Y {};

  void f(S &s) {++s.X::i;}

There is a rule that &s be able to "be implicitly converted to a pointer to the naming class of the right operand" (11.8.3 [class.access.base] paragraph 6), which correctly selects X here, but 7.6.1.5 [expr.ref] bullet 6.2 merely says that

If E2 is a non-static data member [...], the expression designates the corresponding member subobject of the object designated by the first expression.

E2 (i.e. X::i) and Y::i equally denote A::i (since name lookup produces declarations), but there is no rule that indicates which A::i is selected as the "corresponding member subobject". (Note that s.X::A::i means the same as s.A::i and is ambiguous.)

Suggested resolution:

Change 11.8.3 [class.access.base] paragraph 6 to actually perform the conversion it requires, possibly moving it to 7.6.1.5 [expr.ref] since it is not about access control; the definition of "naming class" in 11.8.3 [class.access.base] paragraph 5 would then need to be italicized.




742. Postfix increment/decrement with long bit-field operands

Section: 7.6.1.6  [expr.post.incr]     Status: open     Submitter: Mike Miller     Date: 11 November, 2008

Given the following declarations:

    struct S {
        signed long long sll: 3;
    };
    S s = { -1 };

the expressions s.sll-- < 0u and s.sll < 0u have different results. The reason for this is that s.sll-- is an rvalue of type signed long long (7.6.1.6 [expr.post.incr]), which means that the usual arithmetic conversions (Clause 7 [expr] paragraph 10) convert 0u to signed long long and the result is true. s.sll, on the other hand, is a bit-field lvalue, which is promoted (7.3.7 [conv.prom] paragraph 3) to int; both operands of < have the same rank, so s.sll is converted to unsigned int to match the type of 0u and the result is false. This disparity seems undesirable.




282. Namespace for extended_type_info

Section: 7.6.1.8  [expr.typeid]     Status: open     Submitter: Jens Maurer     Date: 01 May 2001

The original proposed resolution for issue 160 included changing extended_type_info (7.6.1.8 [expr.typeid] paragraph 1, footnote 61) to std::extended_type_info. There was no consensus on whether this name ought to be part of namespace std or in a vendor-specific namespace, so the question was moved into a separate issue.




528. Why are incomplete class types not allowed with typeid?

Section: 7.6.1.8  [expr.typeid]     Status: open     Submitter: Dave Abrahams     Date: 18 May 2005

7.6.1.8 [expr.typeid] paragraph 4 says,

When typeid is applied to a type-id, the result refers to a std::type_info object representing the type of the type-id. If the type of the type-id is a reference type, the result of the typeid expression refers to a std::type_info object representing the referenced type. If the type of the type-id is a class type or a reference to a class type, the class shall be completely-defined.

I'm wondering whether this is not overly restrictive. I can't think of a reason to require that T be completely-defined in typeid(T) when T is a class type. In fact, several popular compilers enforce that restriction for typeid(T), but not for typeid(T&). Can anyone explain this?

Nathan Sidwell: I think this restriction is so that whenever the compiler has to emit a typeid object of a class type, it knows what the base classes are, and can therefore emit an array of pointers-to-base-class typeids. Such a tree is necessary to implement dynamic_cast and exception catching (in a commonly implemented and obvious manner). If the class could be incomplete, the compiler might have to emit a typeid for incomplete Foo in one object file and a typeid for complete Foo in another object file. The compilation system will then have to make sure that (a) those compare equal and (b) the complete Foo gets priority, if that is applicable.

Unfortunately, there is a problem with exceptions that means there still can be a need to emit typeids for incomplete class. Namely one can throw a pointer-to-pointer-to-incomplete. To implement the matching of pointer-to-derived being caught by pointer-to-base, it is necessary for the typeid of a pointer type to contain a pointer to the typeid of the pointed-to type. In order to do the qualification matching on a multi-level pointer type, one has a chain of pointer typeids that can terminate in the typeid of an incomplete type. You cannot simply NULL-terminate the chain, because one must distinguish between different incomplete types.

Dave Abrahams: So if implementations are still required to be able to do it, for all practical purposes, why aren't we letting the user have the benefits?

Notes from the April, 2006 meeting:

There was some concern expressed that this might be difficult under the IA64 ABI. It was also observed that while it is necessary to handle exceptions involving incomplete types, there is no requirement that the RTTI data structures be used for exception handling.




1954. typeid null dereference check in subexpressions

Section: 7.6.1.8  [expr.typeid]     Status: open     Submitter: David Majnemer     Date: 2014-06-23

According to 7.6.1.8 [expr.typeid] paragraph 2,

If the glvalue expression is obtained by applying the unary * operator to a pointer69 and the pointer is a null pointer value (7.3.12 [conv.ptr]), the typeid expression throws an exception (14.2 [except.throw]) of a type that would match a handler of type std::bad_typeid exception (17.7.5 [bad.typeid]).

The footnote makes clear that this requirement applies without regard to parentheses, but it is unspecified whether it applies when the dereference occurs in a subexpression of the operand (e.g., in the second operand of the comma operator or the second or third operand of a conditional operator). There is implementation divergence on this question.




2048. C-style casts that cast away constness vs static_cast

Section: 7.6.1.9  [expr.static.cast]     Status: open     Submitter: Richard Smith     Date: 2014-11-19

According to 7.6.1.9 [expr.static.cast] paragraph 1,

The static_cast operator shall not cast away constness (7.6.1.11 [expr.const.cast]).

However, this phrasing is problematic in the context of a C-style cast like the following:

   const void *p;
   int *q = (int*)p;

The intent of 7.6.3 [expr.cast] is that this should be interpreted as a static_cast followed by a const_cast. However, because int* to const void* is a valid standard conversion, and 7.6.1.9 [expr.static.cast] paragraph 7 allows static_cast to perform the inverse of a standard conversion sequence, the C-style cast is interpreted as just a static_cast without a const_cast and is thus ill-formed.




2668. co_await in a lambda-expression

Section: 7.6.2.4  [expr.await]     Status: open     Submitter: Jim X     Date: 2022-12-12

Subclause 7.6.2.4 [expr.await] paragraph 2 disallows an await-expression to appear in the body of a lambda-expression:

An await-expression shall appear only in a potentially-evaluated expression within the compound-statement of a function-body outside of a handler (14.1 [except.pre]). ...

This is probably unintended.

Possible resolution:

Change in 7.6.2.4 [expr.await] paragraph 2 as follows:

An await-expression shall appear only in as a potentially-evaluated expression within the compound-statement of a function-body or lambda-expression, in either case outside of a handler (14.1 [except.pre]). ...



2609. Padding in class types

Section: 7.6.2.5  [expr.sizeof]     Status: open     Submitter: Jim X     Date: 2022-07-19

Class types may have padding, influencing the result of sizeof. It is unclear whether the placement and amount of padding is implementation-defined, unspecified, or something else. If it is unspecified, the limits of permissible behavior are unclear. Empty classes might need special consideration.

Suggested resolution:

Change in 7.6.2.5 [expr.sizeof] paragraph 2

... When applied to a class, the result is the number of bytes in an object of that class including any padding required for placing objects of that type in an array. The amount and placement of padding in a class type is unspecified. The result of applying sizeof to a potentially-overlapping subobject is the size of the type, not the size of the subobject. [ Footnote: ... ]



267. Alignment requirement for new-expressions

Section: 7.6.2.8  [expr.new]     Status: open     Submitter: James Kuyper     Date: 4 Dec 2000

Requirements for the alignment of pointers returned by new-expressions are given in 7.6.2.8 [expr.new] paragraph 10:

For arrays of char and unsigned char, the difference between the result of the new-expression and the address returned by the allocation function shall be an integral multiple of the most stringent alignment requirement (6.8 [basic.types]) of any object type whose size is no greater than the size of the array being created.

The intent of this wording is that the pointer returned by the new-expression will be suitably aligned for any data type that might be placed into the allocated storage (since the allocation function is constrained to return a pointer to maximally-aligned storage). However, there is an implicit assumption that each alignment requirement is an integral multiple of all smaller alignment requirements. While this is probably a valid assumption for all real architectures, there's no reason that the Standard should require it.

For example, assume that int has an alignment requirement of 3 bytes and double has an alignment requirement of 4 bytes. The current wording only requires that a buffer that is big enough for an int or a double be aligned on a 4-byte boundary (the more stringent requirement), but that would allow the buffer to be allocated on an 8-byte boundary — which might not be an acceptable location for an int.

Suggested resolution: Change "of any object type" to "of every object type."

A similar assumption can be found in 7.6.1.10 [expr.reinterpret.cast] paragraph 7:

...converting an rvalue of type "pointer to T1" to the type "pointer to T2" (where ... the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer value...

Suggested resolution: Change the wording to

...converting an rvalue of type "pointer to T1" to the type "pointer to T2" (where ... the alignment requirements of T1 are an integer multiple of those of T2) and back to its original type yields the original pointer value...

The same change would also be needed in paragraph 9.

Additional note (June, 2022):

Subclause 6.7.6 [basic.align] paragraph 4 specifies:

... Every alignment value shall be a non-negative integral power of two.

Thus, the situation that a stricter alignment is not an integral multiple of a weaker alignment does not arise.




473. Block-scope declarations of allocator functions

Section: 7.6.2.8  [expr.new]     Status: open     Submitter: John Spicer     Date: 12 Jul 2004

Looking up operator new in a new-expression uses a different mechanism from ordinary lookup. According to 7.6.2.8 [expr.new] paragraph 9,

If the new-expression begins with a unary :: operator, the allocation function's name is looked up in the global scope. Otherwise, if the allocated type is a class type T or array thereof, the allocation function's name is looked up in the scope of T. If this lookup fails to find the name, or if the allocated type is not a class type, the allocation function's name is looked up in the global scope.

Note in particular that the scope in which the new-expression occurs is not considered. For example,

    void f() {
        void* operator new(std::size_t, void*);
        int* i = new int;    // okay?
    }

In this example, the implicit reference to operator new(std::size_t) finds the global declaration, even though the block-scope declaration of operator new with a different signature would hide it from an ordinary reference.

This seems strange; either the block-scope declaration should be ill-formed or it should be found by the lookup.

Notes from October 2004 meeting:

The CWG agreed that the block-scope declaration should not be found by the lookup in a new-expression. It would, however, be found by ordinary lookup if the allocation function were invoked explicitly.




1628. Deallocation function templates

Section: 7.6.2.8  [expr.new]     Status: open     Submitter: Richard Smith     Date: 2013-02-22

According to 7.6.2.8 [expr.new] paragraphs 18-20, an exception thrown during the initialization of an object allocated by a new-expression will cause a deallocation function to be called for the object's storage if a matching deallocation function can be found. The rules deal only with functions, however; nothing is said regarding a mechanism by which a deallocation function template might be instantiated to free the storage, although 6.7.5.5.3 [basic.stc.dynamic.deallocation] paragraph 2 indicates that a deallocation function can be an instance of a function template.

One possibility for this processing might be to perform template argument deduction on any deallocation function templates; if there is a specialization that matches the allocation function, by the criteria listed in paragraph 20, that function template would be instantiated and used, although a matching non-template function would take precedence as is the usual outcome of overloading between function template specializations and non-template functions.

Another possibility might be to match non-template deallocation functions with non-template allocation functions and template deallocation functions with template allocation functions.

There is a slightly related wording problem in 7.6.2.8 [expr.new] paragraph 21:

If a placement deallocation function is called, it is passed the same additional arguments as were passed to the placement allocation function, that is, the same arguments as those specified with the new-placement syntax.

This wording ignores the possibility of default arguments in the allocation function, in which case the arguments passed to the deallocation function might be a superset of those specified in the new-placement.

(See also issue 1682.)




2532. Kind of pointer value returned by new T[0]

Section: 7.6.2.8  [expr.new]     Status: open     Submitter: Andrey Erokhin     Date: 2022-02-17

A pointer value must have one of the kinds specified in 6.8.4 [basic.compound] paragraph 3:

Every value of pointer type is one of the following:

When allocating an array with no elements, 7.6.2.8 [expr.new] paragraph 10 is silent on the kind of pointer value returned:

When the allocated type is “array of N T” (that is, the noptr-new-declarator syntax is used or the new-type-id or type-id denotes an array type), the new-expression yields a prvalue of type “pointer to T” that points to the initial element (if any) of the array. Otherwise, let T be the allocated type; the new-expression is a prvalue of type “pointer to T” that points to the object created.

Related to that, are p and q allowed to compare equal in the following example?

T *p = new T[0];
T *q = new T;

Some implementations return a pointer past the array cookie for empty arrays, which can compare equal to a pointer to an object obtained from an unrelated allocation. However, if new T[0] is specified to yield a pointer to an object, this behavior violates the rule that pointers to disjoint objects with overlapping lifetimes must not compare equal.




2566. Matching deallocation for uncaught exception

Section: 7.6.2.8  [expr.new]     Status: open     Submitter: Jim X     Date: 2022-04-13

Initialization of an object may terminate via an exception, in which case any dynamically-allocated memory is freed, per 7.6.2.8 [expr.new] paragraph 26:

If any part of the object initialization described above [ Footnote: ... ] terminates by throwing an exception and a suitable deallocation function can be found, the deallocation function is called to free the memory in which the object was being constructed, after which the exception continues to propagate in the context of the new-expression. If no unambiguous matching deallocation function can be found, propagating the exception does not cause the object's memory to be freed.

However, implementations do not consistently support this provision in case the exception remains uncaught:

  #include <iostream>
  struct C {
    void* operator new(std::size_t n) {
      std::cout << "malloc\n";
      return malloc(n);
    }
    void operator delete(void* ptr) {
      std::cout << "free\n";
      free(ptr);
    }
    C() {
      throw 0;
    }
  };
  int main() {
    auto ptr = new C;
  }

Both clang and GCC do not free the memory in this example; they do so if the exception is caught in main.

Maybe a similar provision as used for stack unwinding in 14.4 [except.handle] paragraph 9 is desirable:

If no matching handler is found, the function std::terminate is invoked; whether or not the stack is unwound before this invocation of std::terminate is implementation-defined (14.6.2 [except.terminate]).

Suggested resolution:

Integrate freeing dynamically-allocated memory with stack unwinding (14.3 [except.ctor]), since this is what implementations actually do.




2592. Missing definition for placement allocation/deallocation function

Section: 7.6.2.8  [expr.new]     Status: open     Submitter: Jim X     Date: 2022-04-14

Subclause 7.6.2.8 [expr.new] has multiple references to "placement allocation function" and "placement deallocation function", but those terms are never defined. The term "usual deallocation function" is defined in 6.7.5.5.3 [basic.stc.dynamic.deallocation] paragraph 3:

... A usual deallocation function is a deallocation function whose parameters after the first are

Possible resolution:

  1. Split 6.7.5.5.2 [basic.stc.dynamic.allocation] paragraph 1 and change it as follows:

    ... The value of the first parameter is interpreted as the requested size of the allocation. A usual allocation function is an allocation function with no parameters after the first or with a single parameter of type std::align_val_t after the first. A placement allocation function is an allocation function that is not a usual allocation function.

    An allocation function can be a function template. ...

  2. Split 6.7.5.5.3 [basic.stc.dynamic.deallocation] paragraph 3 and change it as follows:

    ... A destroying operator delete shall be a usual deallocation function. A placement deallocation function is a deallocation function that is not a usual deallocation function.

    A deallocation function may be an instance of a function template. ...




196. Arguments to deallocation functions

Section: 7.6.2.9  [expr.delete]     Status: open     Submitter: Matt Austern     Date: 20 Jan 2000

7.6.2.8 [expr.new] paragraph 10 says that the result of an array allocation function and the value of the array new-expression from which it was invoked may be different, allowing for space preceding the array to be used for implementation purposes such as saving the number of elements in the array. However, there is no corresponding description of the relationship between the operand of an array delete-expression and the argument passed to its deallocation function.

6.7.5.5.3 [basic.stc.dynamic.deallocation] paragraph 3 does state that

the value supplied to operator delete[](void*) in the standard library shall be one of the values returned by a previous invocation of either operator new[](std::size_t) or operator new[](std::size_t, const std::nothrow_t&) in the standard library.

This statement might be read as requiring an implementation, when processing an array delete-expression and calling the deallocation function, to perform the inverse of the calculation applied to the result of the allocation function to produce the value of the new-expression. (7.6.2.9 [expr.delete] paragraph 2 requires that the operand of an array delete-expression "be the pointer value which resulted from a previous array new-expression.") However, it is not completely clear whether the "shall" expresses an implementation requirement or a program requirement (or both). Furthermore, there is no direct statement about user-defined deallocation functions.

Suggested resolution: A note should be added to 7.6.2.9 [expr.delete] to clarify that any offset added in an array new-expression must be subtracted in the array delete-expression.




2593. Insufficient base class restriction for pointer-to-member expression

Section: 7.6.4  [expr.mptr.oper]     Status: open     Submitter: Hubert Tong     Date: 2022-06-04

Consider:

  struct A {};
  struct AA : A { int y; };
  struct B : A { int x; };
  struct C : AA, B {};

  constexpr int f(const A &a) {
    int A::*mp = static_cast<int A::*>(&B::x);
    return a.*mp;
  }

  extern char x[f(static_cast<const AA &>(C{{{}, 13}, {{}, 42}}))];
  extern char x[13];

Subclause 7.6.4 [expr.mptr.oper] paragraph 4 specifies:

Abbreviating pm-expression.*cast-expression as E1.*E2, E1 is called the object expression. If the dynamic type of E1 does not contain the member to which E2 refers, the behavior is undefined.

In the example, the dynamic type of a is C, which does contain B::x, and the undefined behavior provision does not trigger. Thus the call to f is required to yield 42; however common implementations produce 13. The behavior for this case ought to be undefined.

Suggested resolution:

Change in 7.6.4 [expr.mptr.oper] paragraph 4 as follows:

Abbreviating pm-expression.*cast-expression as E1.*E2, E1 is called the object expression. If the dynamic type of E1 does not contain the member to which E2 refers, Where the type of E2 is "pointer to member of T", C is the (unique) class of which the member to which E2 refers is a direct member, and B is the object of type T that either is the result of E1 or is the uniquely so-typed base subobject thereof, if B is neither of type C nor a base class subobject of an object of type C, then the behavior is undefined.



2548. Array prvalues and additive operators

Section: 7.6.6  [expr.add]     Status: open     Submitter: Andrey Erokhin     Date: 2022-03-08

Consider

  int main()
  {
    using IA = int[];
    IA{ 1, 2, 3 } + 0;
  }

This appears to be ill-formed given the current wording, because the operand is already a prvalue, thus 7.2.1 [basic.lval] paragraph 6 does not apply and the array-to-pointer conversion is not applied:

Whenever a glvalue appears as an operand of an operator that expects a prvalue for that operand, the lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), or function-to-pointer (7.3.4 [conv.func]) standard conversions are applied to convert the expression to a prvalue.

This outcome might be an oversight in the resolution for issue 1232.

See also clang issue 54016.




2696. Relational comparisons of pointers to void

Section: 7.6.9  [expr.rel]     Status: open     Submitter: CWG     Date: 2023-02-09     Liaison: SG22

While discussing issue 2526, it was noted that C++ yields an unspecified result for comparing unequal pointers to void in all cases, whereas C does specify the result if the pointer values point to the same object or (possibly different) subobjects thereof, because C considers void an object type.

February, 2023

Requested confirmation of the C status quo from SG22.




1255. Definition problems with constexpr functions

Section: 7.7  [expr.const]     Status: open     Submitter: Nikolay Ivchenkov     Date: 2011-03-08

The current wording of the Standard is not sufficiently clear regarding the interaction of class scope (which treats the bodies of member functions as effectively appearing after the class definition is complete) and the use of constexpr member functions within the class definition in contexts requiring constant expressions. For example, an array bound cannot use a constexpr member function that relies on the completeness of the class or on members that have not yet been declared, but the current wording does not appear to state that.

Additional note (October, 2013):

This question also affects function return type deduction (the auto specifier) in member functions. For example, the following should presumably be prohibited, but the current wording is not clear:

  struct S {
    static auto f() {
      return 42;
    }
    auto g() -> decltype(f()) {
      return f();
    }
  };



1256. Unevaluated operands are not necessarily constant expressions

Section: 7.7  [expr.const]     Status: open     Submitter: Nikolay Ivchenkov     Date: 2011-03-08

The current definition of constant expressions appears to make unevaluated operands constant expressions; for example, new char[10] would seem to be a constant expression if it appears as the operand of sizeof. This seems wrong.




1626. constexpr member functions in brace-or-equal-initializers

Section: 7.7  [expr.const]     Status: open     Submitter: Daveed Vandevoorde     Date: 2013-02-19

The Standard should make clear that a constexpr member function cannot be used in a constant expression until its class is complete. For example:

  template<typename T> struct C {
    template<typename T2> static constexpr bool _S_chk() {
      return false;
    }
    static const bool __value = _S_chk<int>();
  };

  C<double> c;

Current implementations accept this, although they reject the corresponding non-template case:

  struct C {
    static constexpr bool _S_chk() { return false; }
    static const bool __value = _S_chk();
  };

  C c;

Presumably the template case should be handled consistently with the non-template case.




2192. Constant expressions and order-of-eval undefined behavior

Section: 7.7  [expr.const]     Status: open     Submitter: Peter Sommerlad     Date: 2015-10-27

Notes from the November, 2016 meeting:

CWG did not wish to require implementations to detect this kind of undefined behavior in determining whether an expression is constant or not, but an implementation should be permitted to reject such expressions. These should be indeterminately sequenced, not unsequenced.




2301. Value-initialization and constexpr constructor evaluation

Section: 7.7  [expr.const]     Status: open     Submitter: Daveed Vandevoorde     Date: 2016-04-18

Consider the following example:

  union A {
    constexpr A(int) : x(++x) { }
    int x;
    char* y;
  };
  union B {
    A a = 5;
  };
  int arr[B().a.x];

Value-initialization of the object created by B() zero-initializes the object (9.4 [dcl.init] bullet 8.2), which should mean that the ++x in the mem-initilizer for A operates on a zero-initialized object, but current implementations reject this code as non-constant. It is not clear what in the current wording justifies this treatment.




2456. Viable user-defined conversions in converted constant expressions

Section: 7.7  [expr.const]     Status: open     Submitter: Mike Miller     Date: 2020-08-17

Consider an example like the following:

  struct A {
    constexpr A(int i) : val(i) { }
    constexpr operator int() const { return val; }
    constexpr operator float() const { return val; }
  private:
    int val;
  };
  constexpr A a = 42;
  int ary[a];

According to 9.3.4.5 [dcl.array] paragraph 1, the array bound expression

shall be a converted constant expression of type std::size_t (7.7 [expr.const]).

The user-defined conversion to float would involve a floating-integral conversion (7.3.11 [conv.fpint]; however, such a conversion is not permitted by the list of acceptable conversions in 7.7 [expr.const] paragraph 10:

A converted constant expression of type T is an expression, implicitly converted to type T, where the converted expression is a constant expression and the implicit conversion sequence contains only

and where the reference binding (if any) binds directly.

It is not clear whether this list is intended to restrict the set of viable user-defined conversions, and there is implementation divergence on this point: clang accepts the example above, while g++ rejects it, presumably on the basis of an ambiguous conversion.

Notes from the August, 2020 teleconference:

No direction was established pending information about why the example is accepted by clang.

Additional note, December, 2020:

The clang behavior turns out to have been an oversight, corrected in the current version, so the example is now rejected by both compilers. However, it is unclear that this is desirable. In particular, given the example above, a can be used without error as a bit-field width, as an enumerator value, and as the operand of alignas. Presumably the difference between these integral constant expression contexts and an array bound is the fact that the target type is known to be size_t. However, both bit-field widths and alignas operands are also required to be non-negative. Furthermore, the definition of an “erroneous” array bound in 7.6.2.8 [expr.new] paragraph 9 goes to awkward lengths to check for negative values as the result of user-defined conversions, which might argue in favor of reconsidering the converted constant expression treatment of array bounds.

Notes from the February, 2021 teleconference:

CWG agreed with the considerations in the December, 2020 note, feeling that the difference in treatment between integral constant expressions and a converted constant expression to a specific integral type is somewhat gratuitous. However, it was felt that code like that of the example was unlikely to occur often in real-world code.




2536. Partially initialized variables during constant initialization

Section: 7.7  [expr.const]     Status: open     Submitter: Barry Revzin     Date: 2022-02-21     Liaison: EWG

Consider:

  struct A { int x = 1; int y; };
  constinit A a;                   // static storage duration; #1

The treatment of this example changed with P1331R2 (Permitting trivial default initialization in constexpr contexts), adopted 2019-07. Prior to this paper, the default constructor of A was not constexpr because it left a data member uninitialized. With paper P1331, the restriction was shifted to reading uninitialized objects during constant evaluation, and the variable a now satisfies the requirements for "constant-initialized" in 7.7 [expr.const] paragraph 2:

A variable or temporary object o is constant-initialized if

Zero-initialization is not performed prior to constant-initialization per 6.9.3.2 [basic.start.static] paragraph 2:

Constant initialization is performed if a variable or temporary object with static or thread storage duration is constant-initialized (7.7 [expr.const]). If constant initialization is not performed, a variable with static storage duration (6.7.5.2 [basic.stc.static]) or thread storage duration (6.7.5.3 [basic.stc.thread]) is zero-initialized (9.4 [dcl.init]). Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization.

Thus, #1 is valid and a is statically initialized, but a.y would remain uninitialized, which is surprising for an object with static storage duration.

Current implementations diagnose an error at #1, because the variable a is actually not considered to be constant-initialized.

This issue is closely related to issue 2558.

Suggested resolution [SUPERSEDED]:

Change in 7.7 [expr.const] paragraph 2:
A variable or temporary object o is constant-initialized if

Alternative suggested resolution (March, 2022) [SUPERSEDED]:

Change in 7.7 [expr.const] paragraph 11 as follows:

A constant expression is either a glvalue core constant expression that refers to an entity that is a permitted result of a constant expression (as defined below), or a prvalue core constant expression whose value satisfies the following constraints:

Notes from the November, 2022 meeting

CWG preferred to zero-initialize a.y in the example, and keep #1 well-formed.

Possible resolution:

Change in 6.9.3.2 [basic.start.static] paragraph 2 as follows:

Constant initialization is performed if a variable or temporary object with static or thread storage duration is constant-initialized (7.7 [expr.const]). If constant initialization is not performed, a A variable with static storage duration (6.7.5.2 [basic.stc.static]) or thread storage duration (6.7.5.3 [basic.stc.thread]) or a subobject thereof is zero-initialized (9.4 [dcl.init]) if constant initialization is not performed or if it does not initialize that subobject. Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization. All static initialization strongly happens before (6.9.2.2 [intro.races]) any dynamic initialization.

CWG 2022-12-02

The resolution shown above would leave padding bits uninitialized. In contrast, zero-initialization does set padding bits to 0 to possibly facilitate memcmp. Additional example:

  struct C { 
    int a;
    int b;
    C() : b(a) {}   // #1
  }; 
  constinit C x;    // OK when zero-initializing first, because #1 reads zero-initialized a?

2022-12-03

Forwarded to EWG with cplusplus/papers#1380.

Additional notes (January, 2023)

The standard does not guarantee stable results when reading padding bits, i.e. bits that are not part of the value representation of some in-lifetime object. In C, explicit rules keep padding bits stable; they are allowed to change only if a store to any class member occurs.




2545. Transparently replacing objects in constant expressions

Section: 7.7  [expr.const]     Status: open     Submitter: Richard Smith     Date: 2022-03-05

7.7 [expr.const] paragraph 6 specifies that std::construct_at can be used during constant evaluation:

Similarly, the evaluation of a call to std::construct_at or std::ranges::construct_at does not disqualify E from being a core constant expression unless the first argument, of type T*, does not point to storage allocated with std::allocator<T> or to an object whose lifetime began within the evaluation of E, or the evaluation of the underlying constructor call disqualifies E from being a core constant expression.

Apparently, implementations are required to track whether an object is transparently replaceable (6.7.3 [basic.life] paragraph 8) during constant evaluation to satisfy 7.7 [expr.const] bullet 5.8, which requires that undefined behavior be detected and rejected during constant evaluation:

For example,

  struct A {
    int x, y;
  };
  struct B {
    float a;
    int b;
  };
  union C {
    A a;
    B b;
  };
  constexpr int f() {
   C c = {};
   std::construct_at(&c.b.b, 5);
   // Does this return 5 if c.a.y and c.b.b are laid out at the same address?
   return c.a.y;
  }

No known implementation diagnoses the violation of the rules for transparently replaceable in the following example, but there is implementation divergence for the results of f():

  #include <memory>

  struct A {
    virtual constexpr char f() { return 'A'; }
  };
  struct B : A {
    constexpr char f() override { return 'B'; }
  };

  constexpr char f() {
    B b;
    A *p = &b;
    std::construct_at(p);
    return p->f();     // alternative: return b.f()
  }



2552. Constant evaluation of non-defining variable declarations

Section: 7.7  [expr.const]     Status: open     Submitter: Hubert Tong     Date: 2022-03-21

Paper P2242 (Non-literal variables (and labels and gotos) in constexpr functions) added 7.7 [expr.const] bullet 5.2:

It seems that block-scope extern (i.e. non-defining) declarations are covered by the above bullet, but only definitions should be in view here.

Suggested resolution:

  1. Change in 7.7 [expr.const] bullet 5.2 as follows:

    • a control flow that passes through a declaration definition of a variable with static (6.7.5.2 [basic.stc.static]) or thread (6.7.5.3 [basic.stc.thread]) storage duration;
  2. Change in 8.8 [stmt.dcl] paragraph 3 as follows:

    Dynamic initialization of a block variable with static storage duration (6.7.5.2 [basic.stc.static]) or thread storage duration (6.7.5.3 [basic.stc.thread]) is performed the first time control passes through its declaration definition; such a variable is considered initialized upon the completion of its initialization. If the initialization exits by throwing an exception, the initialization is not complete, so it will be tried again the next time control enters the declaration definition. If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization. [Note: ... —end note] If control re-enters the declaration definition recursively while the variable is being initialized, the behavior is undefined.



2559. Defaulted consteval functions

Section: 7.7  [expr.const]     Status: open     Submitter: Aaron Ballman     Date: 2022-03-29

Consider:

  template <typename Ty>
  struct S {
    Ty i;
    consteval S() = default;
  };

  template <typename Ty>
  struct T {
    Ty i;
    consteval T() {}
  };

  S<int> one; // only Clang rejects
  T<int> two; // Clang, GCC, ICC, MSVC reject

  void locals() {
    S<int> three; // only Clang rejects
    T<int> four;  // Clang, GCC, ICC, MSVC reject
  }

A consteval function should always be evaluated at compile time and never fall back to runtime, thus all four cases should be rejected. Issue 2558 is related.




2633. typeid of constexpr-unknown dynamic type

Section: 7.7  [expr.const]     Status: open     Submitter: Jim X     Date: 2022-09-18

Consider the example in 7.7 [expr.const] paragraph 7:

extern Swim dc;
constexpr auto& sandeno  = typeid(dc);     // OK, can only be typeid(Swim)

The comment in the example seems not to be backed by normative text. In particular, the dynamic type of dc is constexpr-unknown per 7.7 [expr.const] paragraph 7:

During the evaluation of an expression E as a core constant expression, all id-expressions and uses of *this that refer to an object or reference whose lifetime did not begin with the evaluation of E are treated as referring to a specific instance of that object or reference whose lifetime and that of all subobjects (including all union members) includes the entire constant evaluation. For such an object that is not usable in constant expressions, the dynamic type of the object is constexpr-unknown. ...

Thus, typeid(dc) is not a core constant expression per 7.7 [expr.const] bullet 5.26:




2656. Converting consteval lambda to function pointer in non-immediate context

Section: 7.7  [expr.const]     Status: open     Submitter: Hubert Tong     Date: 2022-11-11

Converting a consteval lambda to a function pointer in a non-immediate context should be immediately-escalating.

Currently, this is well-formed:

  auto x = &*[]() consteval { return 42; };

Suggested resolution:

Make the conversion function of a lambda whose call operator is an immediate function also an immediate function.




2702. Constant destruction of reference members

Section: 7.7  [expr.const]     Status: open     Submitter: Richard Smith     Date: 2023-02-13

Issue 2529 says that a constexpr reference bound to a lifetime-extended temporary is required to have constant destruction. However, that rule overlooks constexpr objects with a reference member bound to a lifetime-extended temporary, recursively, and the case for std::initializer_list lifetime extension.




2710. Loops in constant expressions

Section: 7.7  [expr.const]     Status: open     Submitter: Daniel Krügler     Date: 2023-03-23

Iteration statements such as while and for loops are specified by equivalent code involving goto (8.6.2 [stmt.while] paragraph 2, 8.6.4 [stmt.for] paragraph 1, 8.6.5 [stmt.ranged] paragraph 1). The goto statement cannot be evaluated in constant expressions (7.7 [expr.const] bullet 5.30), thus while and for loops cannot be evaluated in constant expressions. Similar concerns arise for continue (8.7.3 [stmt.cont] paragraph 1).

However, that is neither intended nor existing practice.

Suggested resolution:

Change in 7.7 [expr.const] bullet 5.30 as follows:

An expression E is a core constant expression unless the evaluation of E, following the rules of the abstract machine (6.9.1 [intro.execution]), would evaluate one of the following:



2495. Glvalue result of a function call

Section: 8.7.4  [stmt.return]     Status: open     Submitter: Jim X     Date: 2021-07-04

According to 8.7.4 [stmt.return] paragraph 1,

A return statement with any other operand shall be used only in a function whose return type is not cv void; the return statement initializes the glvalue result or prvalue result object of the (explicit or implicit) function call by copy-initialization (9.4 [dcl.init]) from the operand.

It is not clear what a “glvalue result” is or what it means to initialize it.

Suggested resolution:

A return statement with any other operand shall be used only in a function whose return type is not cv void;. If the function call is a prvalue, the return statement initializes the glvalue result or prvalue result object of the (explicit or implicit) function call by copy-initialization (9.4 [dcl.init]) from the operand. Otherwise, the return statement is equivalent to the following hypothetical declaration

If the operand of the return statement, X, is a comma expression without parentheses, e is (X), otherwise e is X. T is the return type of the function call; the invented variable t is the result of the function call.

Notes from the August, 2021 teleconference:

A simpler approach would be simply to use a phrase like “returned object or reference” in place of the current wording referring to glvalues and prvalues. This change was regarded as editorial. The issue will remain in "review" status until CWG can look over the wording change.




2556. Unusable promise::return_void

Section: 8.7.5  [stmt.return.coroutine]     Status: open     Submitter: Davis Herring     Date: 2022-03-24

Subclause 8.7.5 [stmt.return.coroutine] paragraph 3 specifies:

If p.return_void() is a valid expression, flowing off the end of a coroutine's function-body is equivalent to a co_return with no operand; otherwise flowing off the end of a coroutine's function-body results in undefined behavior.

However, 9.5.4 [dcl.fct.def.coroutine] paragraph 6 suggests:

If searches for the names return_void and return_value in the scope of the promise type each find any declarations, the program is ill-formed. [Note: If return_void is found, flowing off the end of a coroutine is equivalent to a co_return with no operand. Otherwise, flowing off the end of a coroutine results in undefined behavior (8.7.5 [stmt.return.coroutine]). —end note]

The difference is between the conditions "valid expression" and "found by name lookup". Effectively, it means that undefined behavior might result where the implementation could instead diagnose an ill-formed use of return_void (for example, because it is inaccessible, deleted, or the function call requires arguments).

Suggested resolution:

Change in 8.7.5 [stmt.return.coroutine] paragraph 3 as follows:

If p.return_void() is a valid expression a search for the name return_void in the scope of the promise type finds any declarations, flowing off the end of a coroutine's function-body is equivalent to a co_return with no operand; otherwise flowing off the end of a coroutine's function-body results in undefined behavior.



2123. Omitted constant initialization of local static variables

Section: 8.8  [stmt.dcl]     Status: open     Submitter: Hubert Tong     Date: 2015-02-02

According to 8.8 [stmt.dcl] paragraph 4,

The zero-initialization (9.4 [dcl.init]) of all block-scope variables with static storage duration (6.7.5.2 [basic.stc.static]) or thread storage duration (6.7.5.3 [basic.stc.thread]) is performed before any other initialization takes place. Constant initialization (6.9.3.2 [basic.start.static]) of a block-scope entity with static storage duration, if applicable, is performed before its block is first entered.

The fact that a variable need not be constant-initialized if its block is not entered appears to allow inspection of the variable after zero-initialization but before constant initialization:

  constexpr int x = 0;

  auto foo() {
    constexpr static const int *p = &x;
    struct A {
      const int *const &getPtr() { return p; }
    } a;
    return a;
  }

  int xcpy = *decltype(foo()){ }.getPtr();

  int main(void) {
    return xcpy;
  }

For a related example, consider:

  // tu1.cpp
  extern const int a = 1;
  inline auto f() {
    static const int b = a;
    struct A { auto operator()() { return &b; } } a;
    return a;
  }

  // tu2.cpp
  extern const int a;
  inline auto f() {
    static const int b = a;
    struct A { auto operator()() { return &b; } } a;
    return a;
  }
  int main() {
    return *decltype(f())()();
  }

Here, b may or may not have constant initialization, but we don't have an ODR violation.

If we want to support such code, the nicest option would be to say that the ODR requires us to act as if we pick one of the definitions of the inline function, which requires us to make a consistent choice for all static storage duration variables within a given function. Alternatively, we could say that if multiple definitions of a variable disagree over whether it has constant initialization, then it does not, allowing more implementation simplicity and no functional change outside of pathological cases.

Notes from the February, 2016 meeting:

The second example will be dealt with separately under issue 2242. For the first example, the Standard should require that local types can be used outside their function only via a returned object. It was still to be decided whether this should be undefined behavior or an error on use of such a type. It was also noted that the same issue can arise with static member functions.




157. Omitted typedef declarator

Section: 9.1  [dcl.pre]     Status: open     Submitter: Daveed Vandevoorde     Date: 19 Aug 1999

9.1 [dcl.pre] paragraph 3 reads,

In a simple-declaration, the optional init-declarator-list can be omitted only when... the decl-specifier-seq contains either a class-specifier, an elaborated-type-specifier with a class-key (11.3 [class.name] ), or an enum-specifier. In these cases and whenever a class-specifier or enum-specifier is present in the decl-specifier-seq, the identifiers in those specifiers are among the names being declared by the declaration... In such cases, and except for the declaration of an unnamed bit-field (11.4.10 [class.bit] ), the decl-specifier-seq shall introduce one or more names into the program, or shall redeclare a name introduced by a previous declaration. [Example:
    enum { };           // ill-formed
    typedef class { };  // ill-formed
—end example]
In the absence of any explicit restrictions in 9.2.4 [dcl.typedef] , this paragraph appears to allow declarations like the following:
    typedef struct S { };    // no declarator
    typedef enum { e1 };     // no declarator
In fact, the final example in 9.1 [dcl.pre] paragraph 3 would seem to indicate that this is intentional: since it is illustrating the requirement that the decl-specifier-seq must introduce a name in declarations in which the init-declarator-list is omitted, presumably the addition of a class name would have made the example well-formed.

On the other hand, there is no good reason to allow such declarations; the only reasonable scenario in which they might occur is a mistake on the programmer's part, and it would be a service to the programmer to require that such errors be diagnosed.




498. Storage class specifiers in definitions of class members

Section: 9.2.2  [dcl.stc]     Status: open     Submitter: Matt Austern     Date: 13 Jan 2005

Suppose we've got this class definition:

    struct X {
       void f();
       static int n;
    };

I think I can deduce from the existing standard that the following member definitions are ill-formed:

    static void X::f() { }
    static int X::n;

To come to that conclusion, however, I have to put together several things in different parts of the standard. I would have expected to find an explicit statement of this somewhere; in particular, I would have expected to find it in 9.2.2 [dcl.stc]. I don't see it there, or anywhere.

Gabriel Dos Reis: Or in 6.6 [basic.link] which is about linkage. I would have expected that paragraph to say that that members of class types have external linkage when the enclosing class has an external linkage. Otherwise 6.6 [basic.link] paragraph 8:

Names not covered by these rules have no linkage.

might imply that such members do not have linkage.

Notes from the April, 2005 meeting:

The question about the linkage of class members is already covered by 6.6 [basic.link] paragraph 5.




2232. thread_local anonymous unions

Section: 9.2.2  [dcl.stc]     Status: open     Submitter: Mike Herrick     Date: 2016-02-23

It is not clear from the current wording whether the thread_local specifier can be applied to anonymous unions or not. According to 9.2.2 [dcl.stc] paragraph 3,

The thread_local specifier indicates that the named entity has thread storage duration (6.7.5.3 [basic.stc.thread]). It shall be applied only to the names of variables of namespace or block scope and to the names of static data members.

One might think that an anonymous union object would be a “variable,” but the next paragraph seems to treat variables and anonymous unions as distinct:

The static specifier can be applied only to names of variables and functions and to anonymous unions (11.5.2 [class.union.anon]).



2195. Unsolicited reading of trailing volatile members

Section: 9.2.9.2  [dcl.type.cv]     Status: open     Submitter: Hubert Tong     Date: 2015-11-06

Consider:

  struct A {
    ~A();
    double d;
    float f;
  };

  struct B : A { volatile int i; };

  A foo(B *bp) { return *static_cast<A *>(bp); }

Is it okay for the memory associated with bp->i to be accessed by foo?

See also 9.2.9.2 [dcl.type.cv] paragraph 5

The semantics of an access through a volatile glvalue are implementation-defined. If an attempt is made to access an object defined with a volatile-qualified type through the use of a non-volatile glvalue, the behavior is undefined.

Additional notes from the November, 2016 meeting:

See also national body comment CH2, addressed in March, 2017 by P0612R0.




144. Position of friend specifier

Section: 9.2.9.4  [dcl.type.elab]     Status: open     Submitter: Daveed Vandevoorde     Date: 22 Jul 1999

9.2.9.4 [dcl.type.elab] paragraph 1 seems to impose an ordering constraint on the elements of friend class declarations. However, the general rule is that declaration specifiers can appear in any order. Should

    class C friend;
be well-formed?


2634. Avoid circularity in specification of scope for friend class declarations

Section: 9.2.9.4  [dcl.type.elab]     Status: open     Submitter: Jim X     Date: 2022-07-04

Consider:

auto f(struct X* ptr) {
  struct D {
    private:
      int d;
      friend class X;      // #1
  };
  return D{};
}
X* b = 0;
struct X {
  void show() {
    auto t = f(0);
    t.d = 10;              // #2 error: ::X is not a friend of f::D
  }
};

The target scope for #2 is f's block scope, making ::X not a friend of f::D. Thus the access at #2 is ill-formed. Clang disagrees.

Subclause 9.2.9.4 [dcl.type.elab] paragraph 3 specifies:

... If E contains an identifier but no nested-name-specifier and (unqualified) lookup for the identifier finds nothing, E shall not be introduced by the enum keyword and declares the identifier as a class-name. The target scope of E is the nearest enclosing namespace or block scope.

If an elaborated-type-specifier appears with the friend specifier as an entire member-declaration, the member-declaration shall have one of the following forms:

friend class-key nested-name-specifieropt identifier ;
...
Any unqualified lookup for the identifier (in the first case) does not consider scopes that contain the target scope; no name is bound.

This specification is circular in that the target scope that limits unqualified lookup is defined only if the identifier is actually declared, but the identifier is declared only if lookup finds nothing.

Possible resolution:

Change in 9.2.9.4 [dcl.type.elab] paragraph 4 as follows:

... Any unqualified lookup for the identifier (in the first case) does not consider scopes that contain the target nearest enclosing namespace or block scope; no name is bound. [ Note: ... ]



2228. Ambiguity resolution for cast to function type

Section: 9.3.3  [dcl.ambig.res]     Status: open     Submitter: Richard Smith     Date: 2016-02-02     Liaison: EWG

Consider:

  int x = (int()) + 5; 

This is ill-formed, because 9.3.3 [dcl.ambig.res] paragraph 2 specifies:

An ambiguity can arise from the similarity between a function-style cast and a type-id. The resolution is that any construct that could possibly be a type-id in its syntactic context shall be considered a type-id.

and thus int() is interpreted as a type-id instead of as a function-style cast, so this is an ill-formed cast to a function type.

This seems to be the wrong disambiguation for all cases where there is a choice between a C-style cast and a parenthesized expression: in all those cases, the C-style cast interpretation results in a cast to a function type, which is always ill-formed.

Further, there is implementation divergence in the handling of this example:

  struct T { int operator++(int); T operator[](int); };
  int a = (T()[3])++; // not a cast

EWG 2022-11-11

This is tracked in github issue cplusplus/papers#1376.




2671. friend named by a template-id

Section: 9.3.4.1  [dcl.meaning.general]     Status: open     Submitter: Davis Herring     Date: 2022-11-18

Subclause 9.3.4.1 [dcl.meaning.general] paragraph 1 specifies:

... If the unqualified-id occurring in a declarator-id is a template-id, the declarator shall appear in the declaration of a template-declaration (13.7 [temp.decls]), explicit-specialization (13.9.4 [temp.expl.spec]), or explicit-instantiation (13.9.3 [temp.explicit]).

However, that is too restrictive and prevents befriending template specializations, for example:

  template<typename> void f();

  class A {
    friend void f<A>();
  };



504. Should use of a variable in its own initializer require a diagnostic?

Section: 9.3.4.3  [dcl.ref]     Status: open     Submitter: Bjarne Stroustrup     Date: 14 Apr 2005

Split off from issue 453.

It is in general not possible to determine at compile time whether a reference is used before it is initialized. Nevertheless, there is some sentiment to require a diagnostic in the obvious cases that can be detected at compile time, such as the name of a reference appearing in its own initializer. The resolution of issue 453 originally made such uses ill-formed, but the CWG decided that this question should be a separate issue.

Rationale (October, 2005):

The CWG felt that this error was not likely to arise very often in practice. Implementations can warn about such constructs, and the resolution for issue 453 makes executing such code undefined behavior; that seemed to address the situation adequately.

Note (February, 2006):

Recent discussions have suggested that undefined behavior be reduced. One possibility (broadening the scope of this issue to include object declarations as well as references) was to require a diagnostic if the initializer uses the value, but not just the address, of the object or reference being declared:

    int i = i;        // Ill-formed, diagnostic required
    void* p = &p;     // Okay



2550. Type "reference to cv void" outside of a declarator

Section: 9.3.4.3  [dcl.ref]     Status: open     Submitter: Jens Maurer     Date: 2022-03-13

9.3.4.3 [dcl.ref] paragraph 1 specifies:

A declarator that specifies the type “reference to cv void” is ill-formed.

A declarator does not contain the leading decl-specifier-seq of a declaration, so the following example is not covered by the prohibition:

  void f(void& x);

Suggested resolution:

Change in 9.3.4.3 [dcl.ref] paragraph 1 as follows:

A declarator that specifies Forming the type “reference to cv void” is ill-formed.



1790. Ellipsis following function parameter pack

Section: 9.3.4.6  [dcl.fct]     Status: open     Submitter: Daryle Walker     Date: 2013-10-01     Liaison: WG14

Although the current wording permits an ellipsis to immediately follow a function parameter pack, it is not clear that the <cstdarg> facilities permit access to the ellipsis arguments.

Rationale (June, 2014):

CWG felt that this is a question of language design and thus should be considered by EWG before any action.

EWG 2022-11-11

C23 removes the requirement that the last parameter be named for va_start. This is tracked in github issue cplusplus/papers#1374.




2553. Restrictions on explicit object member functions

Section: 9.3.4.6  [dcl.fct]     Status: open     Submitter: Jens Maurer     Date: 2021-12-10

Subclause 9.3.4.6 [dcl.fct] paragraph 6 specifies

A member-declarator with an explicit-object-parameter-declaration shall not include a ref-qualifier or a cv-qualifier-seq and shall not be declared static or virtual.

This does not address the situation when an explicit object member function becomes implicitly virtual by overriding an implicit object member function. That should be prevented.

This also does not address class-specific allocation and deallocation functions, which are implicitly static.

Suggested resolution:

  1. Change in 9.3.4.6 [dcl.fct] paragraph 6 as follows:

    A member-declarator with an explicit-object-parameter-declaration shall not include a ref-qualifier or a cv-qualifier-seq and shall not be declared static or virtual.
  2. Change in 9.3.4.6 [dcl.fct] paragraph 7 as follows:

    ... An implicit object member function is a non-static member function without an explicit object parameter. [ Note: An explicit object member function cannot be static (11.4.9.2 [class.static.mfct]) or virtual (11.7.3 [class.virtual]). -- end note ]
  3. Add a new paragraph after 11.4.9.2 [class.static.mfct] paragraph 1:

    [Note: The rules described in 11.4.2 apply to static member functions. -- end note]

    A static member function (11.4.1 [class.mem.general], 11.4.11 [class.free]) shall not be an explicit object member function (9.3.4.6 [dcl.fct]).

  4. Add a new paragraph before 11.7.3 [class.virtual] paragraph 7 as follows:

    A virtual function shall not be an explicit object member function (9.3.4.6 [dcl.fct]).

    [ Example:

      struct B {
        virtual void g(); // #1
      };
      struct D : B {
        virtual void f(this D&);  // error: explicit object member function cannot be virtual
        void g(this D&);          // overrides #1; error: explicit object member function cannot be virtual
      };
    

    -- end example]

    The ref-qualifier, or lack thereof, ...




361. Forward reference to default argument

Section: 9.3.4.7  [dcl.fct.default]     Status: open     Submitter: Steve Clamage     Date: 17 June 2002

Is this program well-formed?

  struct S {
    static int f2(int = f1()); // OK?
    static int f1(int = 2);
  };
  int main()
  {
    return S::f2();
  }

A class member function can in general refer to class members that are declared lexically later. But what about referring to default arguments of member functions that haven't yet been declared?

It seems to me that if f2 can refer to f1, it can also refer to the default argument of f1, but at least one compiler disagrees.

Notes from the February, 2012 meeting:

Implementations seem to have come to agreement that this example is ill-formed.

Additional note (March, 2013):

Additional discussion has occurred suggesting the following examples as illustrations of this issue:

  struct B {
    struct A { int a = 0; };
    B(A = A());    // Not permitted?
  };

as well as

  struct C {
   struct A { int a = C().n; }; // can we use the default argument here?
   C(int k = 0);
   int n;
  };

  bool f();
  struct D {
   struct A { bool a = noexcept(B()); }; // can we use the default initializer here?
   struct B { int b = f() ? throw 0 : 0; };
  };

(See also issue 325.)

Additional note (October, 2013):

Issue 1330 treats exception-specifications like default arguments, evaluated in the completed class type. That raises the same questions regarding self-referential noexcept clauses that apply to default arguments.

Additional note (November, 2020):

Paper P1787R6, adopted at the November, 2020 meeting, partially addresses this issue.




1609. Default arguments and function parameter packs

Section: 9.3.4.7  [dcl.fct.default]     Status: open     Submitter: Jonathan Caves     Date: 2013-01-25

It is not clear from 9.3.4.7 [dcl.fct.default] whether the following is well-formed or not:

  template<typename... T>
  void f2(int a = 0, T... b, int c = 1);

  f2<>(); // parameter a has the value 0 and parameter c has the value 1

(T... b is a non-deduced context per 13.10.3.6 [temp.deduct.type] paragraph 5, so the template arguments must be specified explicitly.)

Notes from the April, 2013 meeting:

CWG agreed that the example should be ill-formed.

Additional note (August, 2013):

9.3.4.7 [dcl.fct.default] paragraph 4 explicitly allows for a function parameter pack to follow a parameter with a default argument:

In a given function declaration, each parameter subsequent to a parameter with a default argument shall have a default argument supplied in this or a previous declaration or shall be a function parameter pack.

However, any instantiation of such a function template with a non-empty pack expansion would result in a function declaration in which one or more parameters without default arguments (from the pack expansion) would follow a parameter with a default argument and thus would be ill-formed. Such a function template declaration thus violates 13.8 [temp.res] paragraph 8:

If every valid specialization of a variadic template requires an empty template parameter pack, the template is ill-formed, no diagnostic required.

Although the drafting review teleconference of 2013-08-26 suggested closing the issue as NAD, it is being kept open to discuss and resolve this apparent contradiction.

Notes from the September, 2013 meeting:

CWG agreed that this example should be accepted; the restriction on default arguments applies to the template declaration itself, not to its specializations.




2701. Default arguments in multiple scopes / inheritance of array bounds in the same scope

Section: 9.3.4.7  [dcl.fct.default]     Status: open     Submitter: Richard Smith     Date: 2020-11-20

It appears that P1787R6 has inadvertently changed the rules for where default arguments can be (re-)declared for a parameter. Consider:

namespace N {
  void f(int a, int b = 1);
}
void N::f(int a = 1, int b) {} // valid before P1787R6, invalid afterwards
void N::f(int a, int b = 2) {} // invalid before P1787R6, valid afterwards,
  // but this default argument will either never be used or will make any call using it ill-formed (unclear which)

Subclause 9.3.4.7 [dcl.fct.default] paragraph 4 specifies:

For non-template functions, default arguments can be added in later declarations of a function that inhabit the same scope. Declarations that inhabit different scopes have completely distinct sets of default arguments. ...

The problem is that the out-of-line declaration of N::f inhabits the global namespace scope, not the scope of namespace N.

A similar problem exists for inheriting array bounds per 9.3.4.5 [dcl.array] paragraph 8:

Furthermore, if there is a reachable declaration of the entity that inhabits the same scope in which the bound was specified, an omitted array bound is taken to be the same as in that earlier declaration, and similarly for the definition of a static data member of a class.

Possible resolution:

  1. Change in 6.4.1 [basic.scope.scope] paragraph 2 as follows:

    ... The host scope of a declaration is the inhabited scope if that scope is a block scope and the target scope otherwise. An entity belongs to a scope S if S is the target scope of a declaration of the entity.
  2. Change in 9.3.4.5 [dcl.array] paragraph 8 as follows:

    Furthermore, if there is a reachable declaration of the entity that inhabits has the same host scope in which the bound was specified, an omitted array bound is taken to be the same as in that earlier declaration, and similarly for the definition of a static data member of a class.
  3. Change in 9.3.4.7 [dcl.fct.default] paragraph 4 as follows:

    For non-template functions, default arguments can be added in later declarations of a function that inhabit have the same host scope. Declarations that inhabit have different host scopes have completely distinct sets of default arguments. ...
  4. Change in 9.3.4.7 [dcl.fct.default] paragraph 9 as follows:

    When an overload set contains a declaration of a function that inhabits a whose host scope is S, any default argument associated with any reachable declaration that inhabits whose host scope is S is available to the call.
  5. Change in 12.2.4.1 [over.match.best.general] paragraph 4 as follows:

    If the best viable function resolves to a function for which multiple declarations were found, and if any two of these declarations inhabit have different host scopes and specify a default argument that made the function viable, the program is ill-formed.



2708. Parenthesized initialization of arrays

Section: 9.4.1  [dcl.init.general]     Status: open     Submitter: Mike Miller     Date: 2023-03-14

Consider:

  const int arr[2](1,2);

This is accepted by all major implementations, yet 9.4.1 [dcl.init.general] paragraph 13 prohibits it:

If the entity being initialized does not have class type, the expression-list in a parenthesized initializer shall be a single expression.

Presumably, this was an oversight when adding parenthesized aggregate initialization.

Possible resolution:

Change in 9.4.1 [dcl.init.general] paragraph 13 as follows:

If the entity being initialized does not have class or array type, the expression-list in a parenthesized initializer shall be a single expression.



2709. Parenthesized initialization of reference-to-aggregate

Section: 9.4.1  [dcl.init.general]     Status: open     Submitter: Mike Miller     Date: 2023-03-14

Paper P0960R3 enabled initialization of aggregates (including arrays) from parenthesized expression lists. The rather similar case of initializating a reference to an aggregate was not addressed. For example:

  const int (&ra)[2](1,2);

There is no evidence in 9.4.1 [dcl.init.general] paragraph 16 that such support is intended, nor does 9.4.4 [dcl.init.ref] paragraph 5 admit an initializer other than a (single) expression for a reference.

There is implementation divergence: gcc accepts, others reject. Also, the parenthesized aggregate initialization paper P0960R3 apparently originally envisioned a textual transformation to a braced-init-list, which presumably would have permitted the example. It is not clear whether the reference case was intentionally or accidentially dropped during the multiple revisions of the paper.

Additional notes (March, 2023)

Forwarded to EWG for consideration via cplusplus/papers#1494 by decision of the CWG chair.




233. References vs pointers in UDC overload resolution

Section: 9.4.4  [dcl.init.ref]     Status: open     Submitter: Matthias Meixner     Date: 9 Jun 2000

There is an inconsistency in the handling of references vs pointers in user defined conversions and overloading. The reason for that is that the combination of 9.4.4 [dcl.init.ref] and 7.3.6 [conv.qual] circumvents the standard way of ranking conversion functions, which was probably not the intention of the designers of the standard.

Let's start with some examples, to show what it is about:

    struct Z { Z(){} };

    struct A {
       Z x;

       operator Z *() { return &x; }
       operator const Z *() { return &x; }
    };

    struct B {
       Z x;

       operator Z &() { return x; }
       operator const Z &() { return x; }
    };

    int main()
    {
       A a;
       Z *a1=a;
       const Z *a2=a; // not ambiguous

       B b;
       Z &b1=b;
       const Z &b2=b; // ambiguous
    }

So while both classes A and B are structurally equivalent, there is a difference in operator overloading. I want to start with the discussion of the pointer case (const Z *a2=a;): 12.2.4 [over.match.best] is used to select the best viable function. Rule 4 selects A::operator const Z*() as best viable function using 12.2.4.3 [over.ics.rank] since the implicit conversion sequence const Z* -> const Z* is a better conversion sequence than Z* -> const Z*.

So what is the difference to the reference case? Cv-qualification conversion is only applicable for pointers according to 7.3.6 [conv.qual]. According to 9.4.4 [dcl.init.ref] paragraphs 4-7 references are initialized by binding using the concept of reference-compatibility. The problem with this is, that in this context of binding, there is no conversion, and therefore there is also no comparing of conversion sequences. More exactly all conversions can be considered identity conversions according to 12.2.4.2.5 [over.ics.ref] paragraph 1, which compare equal and which has the same effect. So binding const Z* to const Z* is as good as binding const Z* to Z* in terms of overloading. Therefore const Z &b2=b; is ambiguous. [12.2.4.2.5 [over.ics.ref] paragraph 5 and 12.2.4.3 [over.ics.rank] paragraph 3 rule 3 (S1 and S2 are reference bindings ...) do not seem to apply to this case]

There are other ambiguities, that result in the special treatment of references: Example:

    struct A {int a;};
    struct B: public A { B() {}; int b;};

    struct X {
       B x;
       operator A &() { return x; }
       operator B &() { return x; }
    };

    main()
    {
       X x;
       A &g=x; // ambiguous
    }

Since both references of class A and B are reference compatible with references of class A and since from the point of ranking of implicit conversion sequences they are both identity conversions, the initialization is ambiguous.

So why should this be a defect?

So overall I think this was not the intention of the authors of the standard.

So how could this be fixed? For comparing conversion sequences (and only for comparing) reference binding should be treated as if it was a normal assignment/initialization and cv-qualification would have to be defined for references. This would affect 9.4.4 [dcl.init.ref] paragraph 6, 7.3.6 [conv.qual] and probably 12.2.4.3 [over.ics.rank] paragraph 3.

Another fix could be to add a special case in 12.2.4 [over.match.best] paragraph 1.




2657. Cv-qualification adjustment when binding reference to temporary

Section: 9.4.4  [dcl.init.ref]     Status: open     Submitter: Brian Bi     Date: 2022-11-10

Core issue 2481 was resolved by clarifying that the temporary object in p5.4.2 is cv-qualified if the reference being initialized is cv-qualified. However, this is not the right bullet point for the example given,

  constexpr const int &r = 42;

Such an initialization would actually use bullet 5.3.1 instead. (5.4.2 would be used if the initializer were, for example, 3.14.) We therefore need to make a similar clarification in bullet 5.3, and ideally using the same language.

Suggested resolution:

  1. Change in 9.4.4 [dcl.init.ref] bullet 5.3 as follows:

    Otherwise, if the initializer expression
    • is an rvalue (but not a bit-field) or function lvalue and “cv1 T1” is reference-compatible with “cv2 T2”, or
    • has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be converted to an rvalue or function lvalue of type “cv3 T3”, where “cv1 T1” is reference-compatible with “cv3 T3” (see 12.2.2.7 [over.match.ref]),
    then the initializer expression in the first case and the converted expression in the second case is called the converted initializer. If the converted initializer is a prvalue, let its type be denoted by T4; the temporary materialization conversion (7.3.5 [conv.rval]) is applied, considering the type of the prvalue to be is adjusted to type “cv1 T4” (7.3.6 [conv.qual]) and the temporary materialization conversion (7.3.5 [conv.rval]) is applied. In any case, the reference binds to the resulting glvalue (or to an appropriate base class subobject).
  2. Append to the example in 9.4.4 [dcl.init.ref] bullet 5.3 as follows:

      B&& rrb = x;  // binds directly to the result of operator B
      const int& r2 = 0;  // binds directly to temporary of type const int
    
  3. Change the example in 9.4.4 [dcl.init.ref] bullet 5.4 as follows:

      const double& rcd2 = 2;   // rcd2 refers to temporary with type const double and value 2.0
    



2704. Clarify meaning of "bind directly"

Section: 9.4.4  [dcl.init.ref]     Status: open     Submitter: Brian Bi     Date: 2022-11-11

Currently, 9.4.4 [dcl.init.ref] paragraph 5 specifies:

In all cases except the last (i.e., implicitly converting the initializer expression to the referenced type), the reference is said to bind directly to the initializer expression.

This is intended to refer to both sub-bullets of the last bullet (see issue 1604), but the presentation is unclear.

Suggested resolution:

  1. Change in 9.4.4 [dcl.init.ref] bullet 5.4 as follows:

    Otherwise, the reference is said to bind indirectly to the initializer expression:
    • If T1 or T2 is a class type and T1 is not reference-related to T2, user-defined conversions are considered using the rules for copy-initialization of an object of type “cv1 T1” by user-defined conversion (9.4 [dcl.init], 12.2.2.5 [over.match.copy], 12.2.2.6 [over.match.conv]); the program is ill-formed if the corresponding non-reference copy-initialization would be ill-formed. The result of the call to the conversion function, as described for the non-reference copy-initialization, is then used to direct-initialize the reference. For this direct-initialization, user-defined conversions are not considered.
    • Otherwise, the initializer expression is implicitly converted to a prvalue of type “T1”. The temporary materialization conversion is applied, considering the type of the prvalue to be “cv1 T1”, and the reference is bound to the result.
    If T1 is reference-related to T2:
    • cv1 shall be the same cv-qualification as, or greater cv-qualification than, cv2; and
    • if the reference is an rvalue reference, the initializer expression shall not be an lvalue. [Note 3: This can be affected by whether the initializer expression is move-eligible (7.5.4.2 [expr.prim.id.unqual]). —end note]
  2. Change in 9.4.4 [dcl.init.ref] paragraph 5 as follows:

    In all cases except the last (i.e., implicitly converting the initializer expression to the referenced type), the The reference is said to bind directly to the initializer expression if it does not bind indirectly. [ Example:
      struct S {
        operator int();
      };
      struct T {
        T(int);
      };
      int&& r1 = S(); // binds directly
      T&& r2 = int(); // binds indirectly
      int&& r3 = double(); // binds indirectly
    
    -- end example ]



2168. Narrowing conversions and +/- infinity

Section: 9.4.5  [dcl.init.list]     Status: open     Submitter: Hubert Tong     Date: 2015-08-19

The intended treatment of a floating point infinity with respect to narrowing conversions is not clear. Is std::numeric_limits<double>::infinity() usable in a constant expression, for example, and should that be different from a calculation that results in an infinity?

Notes from the October, 2015 meeting:

CWG requests the assistance of SG6 in resolving this issue.

Notes from the November, 2016 meeting:

SG6 said that arithmetic operations (not conversions) that produce infinity are not allowed in a constant expression. However, using std::numeric_limits<T>::infinity() is okay, but it can't be used as a subexpression. Conversions that produce infinity from non-infinity values are considered to be narrowing conversions.




2638. Improve the example for initializing by initializer list

Section: 9.4.5  [dcl.init.list]     Status: open     Submitter: Shafik Yaghmour     Date: 2022-10-26

Issue 2137 amended the rules for initialization by initializer list, but neglected to add an example.

Suggested resolution:

Change the example in 9.4.5 [dcl.init.list] bullet 3.7 as follows:

struct S {
  S(std::initializer_list<double>); // #1
  S(std::initializer_list<int>);    // #2
  S(std::initializer_list<S>);      // #3
  S();                              // #3#4
  S(const S&);                      // #5

  // ...
};
S s1 = { 1.0, 2.0, 3.0 };  // invoke #1
S s2 = { 1, 2, 3 };        // invoke #2
S s3{s2};                  // invoke #3
S s3s4 = { };              // invoke #3#4
S s5(s4);                  // invoke #5



1962. Type of __func__

Section: 9.5.1  [dcl.fct.def.general]     Status: open     Submitter: Steve Clamage     Date: 2014-07-04     Liaison: EWG

Two questions have arisen regarding the treatment of the type of the __func__ built-in variable. First, some implementations accept

  void f() {
    typedef decltype(__func__) T;
    T x = __func__;
  }

even though T is specified to be an array type.

In a related question, it was noted that __func__ is implicitly required to be unique in each function, and that not only the value but the type of __func__ are implementation-defined; e.g., in something like

  inline auto f() { return &__func__; }

the function type is implementation-specific. These concerns could be addressed by making the value a prvalue of type const char* instead of an array lvalue.

Notes from the May, 2015 meeting:

CWG agreed with the suggested direction.

Rationale (November, 2018):

See also issue 2362, which asks for the ability to use __func__ in a constexpr function. These two goals are incompatible, so EWG input is requested.

EWG 2022-11-11

Paper requested. This is tracked in github issue cplusplus/papers#1375.




2362. __func__ should be constexpr

Section: 9.5.1  [dcl.fct.def.general]     Status: open     Submitter: Anthony Polukhin     Date: 2017-10-23     Liaison: EWG

The definition of __func__ in 9.5.1 [dcl.fct.def.general] paragraph 8 is:

  static const char __func__[] = "function-name";

This prohibits its use in constant expressions, e.g.,

  int main () {
    // error: the value of __func__ is not usable in a constant expression
    constexpr char c = __func__[0];
  }

Notes from the October, 2018 teleconference:

CWG agreed with the proposed change.

Rationale (November, 2018):

See also issue 1962, which asks that the type of __func__ be const char*. These two goals are incompatible, so EWG input is requested.

EWG 2022-11-11

This is tracked in github issue cplusplus/papers#1378.




2547. Defaulted comparison operator function for non-classes

Section: 9.5.2  [dcl.fct.def.default]     Status: open     Submitter: Jim X     Date: 2022-03-07

(See editorial issue 5337.)

Subclause 9.5.2 [dcl.fct.def.default] paragraph 1 specifies:

A function definition whose function-body is of the form = default ; is called an explicitly-defaulted definition. A function that is explicitly defaulted shall

There seem to be no further restrictions on which comparison operator functions are allowed to be defaulted. For example,

  enum E { };
  bool operator==(E, E) = default;  // well-formed?

Subclause 11.10.1 [class.compare.default] paragraph 1 applies only to comparison operator functions "for some class":

A defaulted comparison operator function (12.4.3 [over.binary]) for some class C shall be a non-template function that is

Suggested resolution:

  1. Change in 9.5.2 [dcl.fct.def.default] paragraph 1 as follows:
    A function definition whose function-body is of the form = default ; is called an explicitly-defaulted definition. A function that is explicitly defaulted shall
  2. Change in 11.10.1 [class.compare.default] paragraph 1 as follows:
    A defaulted comparison operator function (12.4.3 [over.binary]) for some class C shall be a non-template function that is
    • a non-static const non-volatile member of some class C having one parameter of type const C& and either no ref-qualifier or the ref-qualifier &, or
    • a friend of some class C having either two parameters of type const C& or two parameters of type C.

    Such a comparison operator function is termed a comparison operator function for class C. A comparison operator function for class C that is defaulted on its first declaration ...




2570. Clarify constexpr for defaulted functions

Section: 9.5.2  [dcl.fct.def.default]     Status: open     Submitter: Gabriel dos Reis     Date: 2022-04-18

After the application of P2448R2, 9.5.2 [dcl.fct.def.default] paragraph 3 reads:

A function explicitly defaulted on its first declaration is implicitly inline (9.2.8 [dcl.inline]), and is implicitly constexpr (9.2.6 [dcl.constexpr]) if it satisfies the requirements for a constexpr function.

It is unclear that no other such defaulted function is implicitly constexpr.

Suggested resolution:

A function explicitly defaulted on its first declaration is implicitly inline (9.2.8 [dcl.inline]), and is implicitly constexpr (9.2.6 [dcl.constexpr]) if it satisfies the requirements for a constexpr function. [Note: Other defaulted functions are not implicitly constexpr. -- end note ]



2562. Exceptions thrown during coroutine startup

Section: 9.5.4  [dcl.fct.def.coroutine]     Status: open     Submitter: Tomasz Kamiński     Date: 2022-04-06

Subclause 9.5.4 [dcl.fct.def.coroutine] seems to miss specification about the behavior of coroutines when an exception is thrown during the early phases of a coroutine evaluation. It is unclear at what point the ownership of the coroutine frame is passed from the compiler to the user, who then needs to call std::coroutine_handle::destroy to destroy and free the coroutine frame, including the parameter copies. The following situations arise:

See also issue 2451.




2563. Initialization of coroutine result object

Section: 9.5.4  [dcl.fct.def.coroutine]     Status: open     Submitter: Tomasz Kamiński     Date: 2022-04-06     Liaison: EWG

Subclause 9.5.4 [dcl.fct.def.coroutine] paragraph 7 specifies:

The expression promise.get_return_object() is used to initialize the returned reference or prvalue result object of a call to a coroutine. The call to get_return_object is sequenced before the call to initial-suspend and is invoked at most once.

It is unclear:

There is implementation divergence.

Note that a user-defined conversion may be involved in the initialization of the coroutine's prvalue result object from get_return_object(). Note also that the return type of get_return_object might be non-copyable and non-movable. However, there are certain programming patterns that would benefit from a late-initialized return value.

See also compiler explorer.

Suggested resolution [SUPERSEDED]:

Change in 9.5.4 [dcl.fct.def.coroutine] paragraph 7 as follows:

The expression promise.get_return_object() is used to initialize the The returned reference or prvalue result object of a call to a coroutine is copy-initialized with promise.get_return_object(). The call to get_return_object initialization is sequenced before the call to initial-suspend and is invoked at most once.

Additional notes (January, 2023)

See also clang bug report #56532.

Forwarded to EWG with paper issue 1414, by decision of the CWG chair.

EWG 2023-02-06

EWG agrees that get_return_object is invoked outside of the try-block and that, if a conversion is needed, the return value of get_return_object is considered an xvalue that is later converted to the result object.




2340. Reference collapsing and structured bindings

Section: 9.6  [dcl.struct.bind]     Status: open     Submitter: Daveed Vandevoorde     Date: 2017-03-29

According to 9.6 [dcl.struct.bind] paragraph 3,

Given the type Ti designated by std::tuple_element<i, E>::type, each vi is a variable of type “reference to Ti” initialized with the initializer, where the reference is an lvalue reference if the initializer is an lvalue and an rvalue reference otherwise; the referenced type is Ti.

If Ti is already a reference type, should this do reference collapsing? Presumably yes, but reference collapsing is specified in terms of a typedef-name or decltype-specifier, which are not used in this description.

See also issue 2313.




813. typename in a using-declaration with a non-dependent name

Section: 9.9  [namespace.udecl]     Status: open     Submitter: UK     Date: 3 March, 2009

N2800 comment UK 101

9.9 [namespace.udecl] paragraph 20 says,

If a using-declaration uses the keyword typename and specifies a dependent name (13.8.3 [temp.dep]), the name introduced by the using-declaration is treated as a typedef-name (9.2.4 [dcl.typedef]).

This wording does not address use of typename in a using-declaration with a non-dependent name; the primary specification of the typename keyword in 13.8 [temp.res] does not appear to describe this case, either.

Additional notes (March, 2022):

The relevant wording is now in 13.8.1 [temp.res.general] paragraph 5:
A name that refers to a using-declarator whose terminal name is dependent is interpreted as a typedef-name if the using-declarator uses the keyword typename.



2555. Ineffective redeclaration prevention for using-declarators

Section: 9.9  [namespace.udecl]     Status: open     Submitter: Christof Meerwald     Date: 2022-03-23

Consider:

  template<int I>
  struct C { };

  struct B
  {
    C<1> foo();
    C<1> bar();
  };

  struct D : B
  {
    using B::foo;
    C<2> foo(this B &);

    using B::bar;
    C<2> bar(this D &);
  };

  struct DD : D
  {
    using D::foo;
    using D::bar;
  };

  void bar(D d, DD dd)
  {
    d.foo();
    dd.foo();

    d.bar();
    dd.bar();
  }

Which functions are called?

Subclause 9.9 [namespace.udecl] paragraph 11 specifies:

The set of declarations named by a using-declarator that inhabits a class C does not include member functions and member function templates of a base class that correspond to (and thus would conflict with) a declaration of a function or function template in C.

The definition of "corresponds" considers the type of the implicit object parameter, which is a deviation from the status quo ante for a simple example like this one:

  struct B {
    void f();    // #1
  };
  struct D : B {
    void f();
    using B::f;  // should not name #1
  };

Suggested resolution:

Change in 9.9 [namespace.udecl] paragraph 11 as follows:

The set of declarations named by a using-declarator that inhabits a class C does not include member functions and member function templates of a base class that, when considered as members of C, correspond to (and thus would conflict with) a declaration of a function or function template in C.

[ Example:

  struct B {
    virtual void f(int);
    virtual void f(char);
    void g(int);
    void h(int);
    void i();
    void j();
  };

  struct D : B {
    using B::f;
    void f(int);   // OK, D::f(int) overrides B::f(int)
  
    using B::g;
    void g(char);  // OK
  
    using B::h;
    void h(int);   // OK, D::h(int) hides B::h(int)

    using B::i;
    void i(this B &);  // OK

    using B::j;
    void j(this D &);  // OK, D::j() hides B::j()
  };

  void k(D* p)
  {
    p->f(1);        // calls D::f(int)
    p->f('a');      // calls B::f(char)
    p->g(1);        // calls B::g(int)
    p->g('a');      // calls D::g(char)
    p->i();         // calls B::i, because B::i as a member of D is a better match than D::i
    p->j();         // calls D::j
  }
  ...



1617. alignas and non-defining declarations

Section: 9.12.2  [dcl.align]     Status: open     Submitter: Richard Smith     Date: 2012-02-02

According to 9.12.2 [dcl.align] paragraph 6,

If the defining declaration of an entity has an alignment-specifier, any non-defining declaration of that entity shall either specify equivalent alignment or have no alignment-specifier. Conversely, if any declaration of an entity has an alignment-specifier, every defining declaration of that entity shall specify an equivalent alignment. No diagnostic is required if declarations of an entity have different alignment-specifiers in different translation units.

Because this is phrased in terms of the definition of an entity, an example like the following is presumably well-formed (even though there can be no definition of n):

   alignas(8) extern int n;
   alignas(16) extern int n;

Is this intentional?




2541. Linkage specifications, module purview, and module attachment

Section: 10.1  [module.unit]     Status: open     Submitter: Nathan Sidwell     Date: 2022-02-28

The interaction between linkage specifications (9.11 [dcl.link]) and named or global module purview and attachment (10.1 [module.unit]) is confusing. The addition of linkage declarations attaching their contents to the global module is not fully integrated into the wording and examples would also help.

Suggested resolution:

  1. Change 6.9.3.1 [basic.start.main] paragraph 1 as follows:

    A program shall contain exactly one function called main that belongs to the global scope and is attached to the global module. Executing a program starts a main thread of execution (6.9.2 [intro.multithread], 33.4 [thread.threads]) in which the main function is invoked. It is implementation-defined whether a program in a freestanding environment is required to define a main function.
  2. Change 10.1 [module.unit] bullet 7.2 as follows:

    • ...
    • Otherwise, if the declaration
      • is a replaceable global allocation or deallocation function (17.6.3.2 [new.delete.single], 17.6.3.3 [new.delete.array]), or
      • is a namespace-definition with external linkage, or
      • appears within a linkage-specification,
      it is attached to the global module.
    • Otherwise, ...
  3. Add an example at the end of 10.1 [module.unit] paragraph 7:

    [ Example:

      // Translation unit #1
      export module Foo;
      void f();              // module linkage, attached to named module Foo
      extern "C++" {
        export void g();     // nameable by importers
        void h();            // nameable in Foo's purview
      }
    

    Both g and h have external linkage, are attached to the global module, and can thus also be redeclared in other translation units:

      // Legacy header "foo.h"
      extern "C++" void g();
    
      // Legacy header "foo-internal.h"
      extern "C++" void h();
    

    -- end example ]

    A module-declaration that contains neither...

  4. Change in 10.2 [module.interface] paragraph 6 as follows:
    A redeclaration of an entity X is implicitly exported if X was introduced by an exported declaration; otherwise it shall not be exported unless it has external linkage..



2637. Injected-class-name as a simple-template-id

Section: 11.1  [class.pre]     Status: open     Submitter: Shafik Yaghmour     Date: 2022-10-26

Issue 2237 sought to disallow simple-template-ids as constructor names, by referring to the injected-class-name. However, 11.1 [class.pre] paragraph 2 specifies:

The class-name is also bound in the scope of the class (template) itself; this is known as the injected-class-name.

The grammar non-terminal class-name includes the option of a simple-template-id (for declaring a partial specialization).

Suggested resolution:

Change in 11.1 [class.pre] paragraph 2 as follows:

The terminal name of the class-name is also bound in the scope of the class (template) itself; this is known as the injected-class-name. ...



511. POD-structs with template assignment operators

Section: 11.2  [class.prop]     Status: open     Submitter: Alisdair Meredith     Date: 19 Mar 2005

A POD-struct is not permitted to have a user-declared copy assignment operator (11.2 [class.prop] paragraph 1). However, a template assignment operator is not considered a copy assignment operator, even though its specializations can be selected by overload resolution for performing copy operations (11.4.6 [class.copy.assign] paragraph 12). Consequently, X in the following code is a POD, notwithstanding the fact that copy assignment (for a non-const operand) is a member function call rather than a bitwise copy:

    struct X {
      template<typename T> const X& operator=(T&);
    };
    void f() {
      X x1, x2;
      x1 = x2;  // calls X::operator=<X>(X&)
    }

Is this intentional?




2463. Trivial copyability and unions with non-trivial members

Section: 11.2  [class.prop]     Status: open     Submitter: Daveed Vandevoorde     Date: 2020-11-30     Liaison: EWG

According to 11.2 [class.prop] paragraph 1,

A trivially copyable class is a class:

This definition has surprising effects in a union whose members are not trivial. For example:

  struct S {
    S& operator=(const S&);
  };
  union U {
    S s;
  };

In this case, S is not trivially copyable because its assignment operator is non-trivial, although its copy constructor is trivial. U, however, is trivially copyable because its assignment operator is not eligible (11.4.4 [special] paragraph 6) because it is deleted, but its copy constructor is trivial, thus satisfying the second bullet.

It is unclear why, for example, a complete object of type S cannot be memcpyed but such an object can be memcpyed when embedded in a union.

There is implementation divergence in the handling of this example.

CWG 2022-11-10

Traditionally, the rule for trivial copyability has been that each of the potentially user-written ways of copying a class (copy/move constructors, copy/move assignment operators) have to be trivial (or deleted). See C++17 subclause 12p6:

A trivially copyable class is a class:

That seems unhelpful. The rule should instead be that if there is any way of copying the class such that the compiler will generate a memcpy (because the corresponding operation is trivial), the user should be allowed to perform memcpy, too. In terms of wording, this amounts to striking the first bullet and adding "trivial" to the second bullet. (The wording in the current working draft considers eligibility, which complicates the treatment slightly in terms unrelated to the present issue.)

CWG is seeking EWG advice on this issue via cplusplus/papers#1363.

Additional notes (March, 2023)

std::tuple with trivially-copyable element types and with no elements (std::tuple<>) ought to be trivially copyable, but the recent addition of const-qualified assignment operators makes that not so under the status quo core language rules.




2188. empty-declaration grammar ambiguity

Section: 11.4.1  [class.mem.general]     Status: open     Submitter: Jens Maurer     Date: 2015-10-21

(The originally reported ambiguity between simple-declaration and empty-declaration does not seem to exist.)

There is a grammar ambiguity between

  empty-declaration:
     ;

and

  member-declaration :
      attribute-specifier-seqopt decl-specifier-seqopt member-declarator-listopt ;
      ...
      empty-declaration



2661. Missing disambiguation rule for pure-specifier vs. brace-or-equal-initializer

Section: 11.4.1  [class.mem.general]     Status: open     Submitter: Richard Smith     Date: 2022-10-07

Subclause 11.4.1 [class.mem.general] has this grammar:

    member-declarator:
        declarator virt-specifier-seq[opt] pure-specifier[opt]
        declarator brace-or-equal-initializer[opt]

    pure-specifier:
        = 0

The primary issue is that foo = 0 matches both member-declarator productions. Secondarily, a declarator by itself is also ambiguous.

Code such as virtual FunctionType f = 0; can be valid, so disambiguation on the syntactic form of the declarator is not possible.

Suggested resolution:

Change and add before 11.4.1 [class.mem.general] paragraph 1 as follows:

    member-declarator:
        declarator virt-specifier-seqopt pure-specifieropt
        declarator brace-or-equal-initializeropt
The token sequence = 0 is treated as a pure-specifier if the type of the declarator-id (9.3.4.1 [dcl.meaning.general]) is a function type, and is otherwise treated as a brace-or-equal-initializer. [ Note: If the member declaration acquires a function type through template instantiation, the program is ill-formed; see 13.9.1 [temp.spec.general]. --end note ]



2595. "More constrained" for eligible special member functions

Section: 11.4.4  [special]     Status: open     Submitter: Barry Revzin     Date: 2022-06-08

Consider:

  #include <type_traits>

  template<typename T>
  concept Int = std::is_same_v<T, int>;

  template<typename T>
  concept Float = std::is_same_v<T, float>;

  template<typename T>
  struct Foo {
    Foo() requires Int<T> = default; // #1
    Foo() requires Int<T> || Float<T> = default; // #2
  };

Per the wording, #1 is not eligible for Foo<float>, because the constraints are not satisfied. But #2 also is not eligible, because #1 is more constrained than #2. The intent is that #2 is eligible.

Suggested resolution:

Change in 11.4.4 [special] paragraph 6 as follows:

An eligible special member function is a special member function for which:



2513. Ambiguity with requires-clause and operator-function-id

Section: 11.4.8.3  [class.conv.fct]     Status: open     Submitter: Richard Smith     Date: 2021-12-09

An ambiguity can occur with a requires-clause that ends in an operator-function-id:

  template<typename T> requires T::operator int
  const // part of operator-type-id or return type?
  unsigned f();

  template<typename T> requires T::operator int
  [[attr]] // appertains to type int or to declaration of g?
  void g();

Such cases are always ill-formed, because they involve an atomic constraint of non-bool type.




57. Empty unions

Section: 11.5  [class.union]     Status: open     Submitter: Steve Adamczyk     Date: 13 Oct 1998

There doesn't seem to be a prohibition in 11.5 [class.union] against a declaration like

    union { int : 0; } x;
Should that be valid? If so, 9.4 [dcl.init] paragraph 5 third bullet, which deals with default-initialization of unions, should say that no initialization is done if there are no data members.

What about:

    union { } x;
    static union { };
If the first example is well-formed, should either or both of these cases be well-formed as well?

(See also the resolution for issue 151.)

Notes from 10/00 meeting: The resolution to issue 178, which was accepted as a DR, addresses the first point above (default initialization). The other questions have not yet been decided, however.




2591. Implicit change of active union member for anonymous union in union

Section: 11.5.1  [class.union.general]     Status: open     Submitter: Richard Smith     Date: 2022-05-29

Subclause 11.5.1 [class.union.general] paragraph 6 describes how union member subobjects are implicitly created by certain assignment operations that assign to union members. However, this description does not appear to properly handle the case of an anonymous union appearing within a union:

  union A {
    int x;
    union {
     int y;
    };
  };
  void f() {
    A a = {.x = 1};
    a.y = 2;
  }

Here, the expectation is that the assignment to a.y starts the lifetime of the anonymous union member subobject within A and also the int member subobject of the anonymous union member subobject. But the algorithm for computing S(a.y) determines that it is {a.y} and does not include the anonymous union member subobject.

Suggested resolution:

Change in 11.5.1 [class.union.general] paragraph 6 as follows:

In an assignment expression of the form E1 = E2 that uses either the built-in assignment operator (7.6.19 [expr.ass]) or a trivial assignment operator (11.4.6 [class.copy.assign]), for each element X of S(E1) and each anonymous union member X (11.5.2 [class.union.anon]) that is a member of a union and has such an element as an immediate subobject (recursively), if modification of X would have undefined behavior under 6.7.3 [basic.life], an object of the type of X is implicitly created in the nominated storage; no initialization is performed and the beginning of its lifetime is sequenced after the value computation of the left and right operands and before the assignment.

Editing note: Adding this rule into the definition of S would be more logical, but S(E) is a set of subexpressions of E and there is no form of expression that names an anonymous union member. Redefining S(E) to be a set of objects might be a better option.




2675. start_lifetime_as, placement-new, and active union members

Section: 11.5.1  [class.union.general]     Status: open     Submitter: Nina Ranns     Date: 2022-12-06

Consider:

  struct A { int i;};
  struct B { int j;};
  union U { A a; B b; };
  U u1{A{}};
  start_lifetime_as<B>(&u1.a); // #1
  start_lifetime_as<B>(&u1);   // #2
  U u2{B{}};
  start_lifetime_as<U>(&u2);   // #3
  start_lifetime_as<B>(&u1.b); // #4

It is unclear which of these constructs changes the active member of the union, and whether a union might have more than one active member. Subclause 11.5.1 [class.union.general] paragraph 2 appears contradictory if several members can be in-lifetime:

In a union, a non-static data member is active if its name refers to an object whose lifetime has begun and has not ended (6.7.3 [basic.life]). At most one of the non-static data members of an object of union type can be active at any time, that is, the value of at most one of the non-static data members can be stored in a union at any time.

Similar questions arise for placement-new and for construct_at; for the latter also during constant evaluation.




2554. Overriding virtual functions, also with explicit object parameters

Section: 11.7.3  [class.virtual]     Status: open     Submitter: Jens Maurer     Date: 2021-12-10

Consider:

  struct B {
    virtual void f();   // #1
  };

  struct D : B {
    void f();           // #2
  };

Subclause 11.7.3 [class.virtual] paragraph 2 says:

If a virtual member function F is declared in a class B, and, in a class D derived (directly or indirectly) from B, a declaration of a member function G corresponds (6.4.1 [basic.scope.scope]) to a declaration of F, ignoring trailing requires-clauses, then G overrides [ Footnote: ... ] F .

Subclause 6.4.1 [basic.scope.scope] paragraph 4 defines "corresponds" as follows:

Two declarations correspond if they (re)introduce the same name, both declare constructors, or both declare destructors, unless

Subclause 6.4.1 [basic.scope.scope] paragraph 3 defines "corresponding object parameters" as follows:

Two non-static member functions have corresponding object parameters if:

In the example, B::f has an object parameter of type B, but D::f has an object parameter of type D. Thus, the two functions do not correspond, and thus D::f does not override B::f. That is an unintended alteration of the status quo ante.

See also issue 2553.

Suggested resolution:

Change in 11.7.3 [class.virtual] paragraph 2 as follows:

If a virtual member function F is declared in a class B, and, in a class D derived (directly or indirectly) from B, a declaration of a member function G corresponds (6.4.1 [basic.scope.scope]) to a declaration of F considered as a member of D (12.2.2.1 [over.match.funcs.general]), ignoring trailing requires-clauses, and, if both are implicit object member functions, they have the same ref-qualifier (or absence thereof), and, if at least one is an explicit object member function, ignoring object parameters, then G overrides [ Footnote: ... ] F .



718. Non-class, non-function friend declarations

Section: 11.8.4  [class.friend]     Status: open     Submitter: John Spicer     Date: 18 September, 2008     Liaison: EWG

With the change from a scope-based to an entity-based definition of friendship (see issues 372 and 580), it could well make sense to grant friendship to enumerations and variables, for example:

    enum E: int;
    class C {
      static const int i = 5;  // Private
      friend E;
      friend int x;
    };
    enum E { e = C::i; };      // OK: E is a friend
    int x = C::i;              // OK: x is a friend

According to the current wording of 11.8.4 [class.friend] paragraph 3, the friend declaration of E is well-formed but ignored, while the friend declaration of x is ill-formed.

Additional notes (February, 2023)

Forwarding to EWG for review of this extension, per decision of the CWG chair. See cplusplus/papers#1466.




2244. Base class access in aggregate initialization

Section: 11.8.5  [class.protected]     Status: open     Submitter: Richard Smith     Date: 2016-03-08

The rules in 11.8.5 [class.protected] assume an object expression, perhaps implicit, that can be used to determine whether access to protected members is permitted or not. It is not clear how that applies to aggregates and constructors. For example:

  struct A {
  protected:
    A();
  };
  struct B : A {
    friend B f();
    friend B g();
    friend B h();
  };
  B f() { return {}; }     // ok? 
  B g() { return {{}}; }   // ok? 
  B h() { return {A{}}; }  // ok?

Notes from the December, 2016 teleconference:

The consensus favored accepting f and g while rejecting h.

Notes from the March, 2018 meeting:

CWG affirmed the earlier direction and felt that there should be an implicit object expression assumed for these cases.




1915. Potentially-invoked destructors in non-throwing constructors

Section: 11.9.3  [class.base.init]     Status: open     Submitter: Aaron Ballman     Date: 2014-04-15     Liaison: EWG

The requirement in 11.9.3 [class.base.init] paragraph 10,

In a non-delegating constructor, the destructor for each potentially constructed subobject of class type is potentially invoked (11.4.7 [class.dtor]). [Note: This provision ensures that destructors can be called for fully-constructed sub-objects in case an exception is thrown (14.3 [except.ctor]). —end note]

is needlessly restrictive, preventing otherwise-reasonable code like

  class Base {
  protected:
    Base(int i) noexcept {}
    Base() = delete;
    ~Base() = delete;
  };

  class Derived : public Base {
  public:
    Derived() noexcept : Base(1) {}
    ~Derived() = delete;
  };

Since the derived class constructor is non-throwing, the deleted base class destructor need not be referenced.

Suggested resolution:

Change 11.9.3 [class.base.init] paragraph 10 as follows:

In a non-delegating constructor without a non-throwing exception-specification (14.5 [except.spec]), the destructor for each potentially constructed subobject of class type is potentially invoked (11.4.7 [class.dtor]). [Note: This provision ensures that destructors can be called for fully-constructed sub-objects in case an exception is thrown (14.3 [except.ctor]) but does not prevent explicitly deleted destructors in the presence of a non-throwing constructor. —end note]

Rationale (June, 2014):

This request for a language change should be evaluated by EWG before any action is taken.

EWG 2022-11-11

This is a defect, but has ABI impact that should be explored in a paper to EWG. This is tracked in github issue cplusplus/papers#1371.




2669. Lifetime extension for aggregate initialization

Section: 11.9.3  [class.base.init]     Status: open     Submitter: Ed Catmur     Date: 2022-12-18

Issue 1815 presented the following example:

  struct A {};
  struct B { A&& a = A{}; };
  B b1;       // #1
  B b2{A{}};  // #2
  B b3{};     // #3

CWG intended to make #3 behave like #2, i.e. the temporary created in the brace-or-equal initializer is lifetime-extended. Issue 1696 was adopted, ostensibly resolving issue 1815, but making #3 (and #1) ill-formed per 11.9.3 [class.base.init] paragraph 11:

A temporary expression bound to a reference member from a default member initializer is ill-formed. [ Example:
  struct A {
    A() = default;          // OK
    A(int v) : v(v) { }     // OK
    const int& v = 42;      // OK
  };
  A a1;     // error: ill-formed binding of temporary to reference
  A a2(1);  // OK, unfortunately
-- end example]

However, the example does not consider aggregate initialization, and the specified resolution for issue 1696 is contrary to the stated intent in issue 1815.

There is considerable implementation variance: #1 is rejected by clang, but accepted by gcc and EDG (with early destruction of the temporary); #2 is uniformly accepted and the lifetime of the temporary is extended; #3 is uniformly accepted, but only gcc, clang, and MSVC extend the lifetime of the temporary, whereas EDG does not.

Note that 9.4.1 [dcl.init.general] paragraph 7 specifies that default-initialization of aggregates is as-if the initializer () is used:

To default-initialize an object of type T means:

Such treatment causes early destruction of temporaries per 6.7.7 [class.temporary] bullet 6.10:

The exceptions to this lifetime rule are:

CWG 2023-01-06

CWG is soliciting the design guidance of EWG to resolve this issue:

  1. Is #1 in the initial example intended to be ill-formed? If not, is the temporary created in the default member initializer for a intended to be lifetime-extended?
  2. Does the specification treatment of #2 (well-formed; lifetime extension of the A temporary) reflect the design intent?
  3. Is #3 intended to be ill-formed? If not, is the temporary created in the default member initializer for a intended to be lifetime-extended?




6. Should the optimization that allows a class object to alias another object also allow the case of a parameter in an inline function to alias its argument?

Section: 11.9.6  [class.copy.elision]     Status: open     Submitter: unknown     Date: unknown

[Picked up by evolution group at October 2002 meeting.]

(See also paper J16/99-0005 = WG21 N1182.)

At the London meeting, 11.4.5.3 [class.copy.ctor] paragraph 31 was changed to limit the optimization described to only the following cases:

One other case was deemed desirable as well: However, there are cases when this aliasing was deemed undesirable and, at the London meeting, the committee was not able to clearly delimit which cases should be allowed and which ones should be prohibited.

Can we find an appropriate description for the desired cases?

Rationale (04/99): The absence of this optimization does not constitute a defect in the Standard, although the proposed resolution in the paper should be considered when the Standard is revised.

Note (March, 2008):

The Evolution Working Group has accepted the intent of this issue and referred it to CWG for action (not for C++0x). See paper J16/07-0033 = WG21 N2173.

Notes from the June, 2008 meeting:

The CWG decided to take no action on this issue until an interested party produces a paper with analysis and a proposal.




1049. Copy elision through reference parameters of inline functions

Section: 11.9.6  [class.copy.elision]     Status: open     Submitter: Jason Merrill     Date: 2010-03-10

Consider the following example:

    int c;

    struct A {
       A() { ++c; }
       A(const A&) { ++c; }
    };

    struct B {
       A a;
       B(const A& a): a(a) { }
    };

    int main() {
       (B(A()));
       return c - 1;
    }

Here we would like to be able to avoid the copy and just construct the A() directly into the A subobject of B. But we can't, because it isn't allowed by 11.4.5.3 [class.copy.ctor] bullet 34.3:

The part about not being bound to a reference was added for an unrelated reason by issue 185. If that resolution were recast to require that the temporary object is not accessed after the copy, rather than banning the reference binding, this optimization could be applied.

The similar example using pass by value is also not one of the allowed cases, which could be considered part of issue 6.




2568. Access checking during synthesis of defaulted comparison operator

Section: 11.10.1  [class.compare.default]     Status: open     Submitter: Nicolai Josuttis     Date: 2022-04-11

Consider:

  struct Base {
  protected:
    bool operator==(const Base& other) const = default;
  };

  struct Child : Base {
    int i;
    bool operator==(const Child& other) const = default;
  };

Per 11.10.1 [class.compare.default] paragraph 6,

Let xi be an lvalue denoting the i-th element in the expanded list of subobjects for an object x (of length n), where xi is formed by a sequence of derived-to-base conversions (12.2.4.2 [over.best.ics]), class member access expressions (7.6.1.5 [expr.ref]), and array subscript expressions (7.6.1.2 [expr.sub]) applied to x.

The derived-to-base conversion for this loses the context of access to the protected Base::operator==, violating 11.8.5 [class.protected] paragraph 1. The example is rejected by implementations, but ought to work.

For this related example, there is implementation divergence:

  struct B {
  protected:
    constexpr operator int() const { return 0; }
  };
  struct D : B {
    constexpr bool operator==(const D&) const = default;
  };
  template<typename T> constexpr auto comparable(T t) -> decltype(t == t) { return t == t; }
  constexpr bool comparable(...) { return false; }
  static_assert(comparable(D{}));

Is D::operator== deleted, because its defaulted definition violates the protected access rules? Is D::operator== not deleted, but synthesis fails on use because of the proctected access rules? Is the synthesis not in the immediate context, making the expression comparable(D{}) ill-formed?




2546. Defaulted secondary comparison operators defined as deleted

Section: 11.10.4  [class.compare.secondary]     Status: open     Submitter: Jim X     Date: 2022-03-07

(See also editorial issues 5335 and 5336.)

Consider the example in 11.10.4 [class.compare.secondary] paragraph 3:

  struct HasNoLessThan { };
  struct C {
    friend HasNoLessThan operator<=>(const C&, const C&);
    bool operator<(const C&) const = default;  // OK, function is deleted
  };

While the comment may reflect the intent, it does not follow from the wording. 11.10.4 [class.compare.secondary] paragraph 2 specifies:

The operator function with parameters x and y is defined as deleted if

Otherwise, the operator function yields x @ y. The defaulted operator function is not considered as a candidate in the overload resolution for the @ operator.

Overload resolution applied to x < y results in a usable candidate operator<=> (12.2.1 [over.match.general]) and that candidate is a rewritten candidate (12.2.2.3 [over.match.oper] bullet 3.4), thus operator< in the above example is not deleted. However, its definition is ill-formed, because the rewrite (x <=> y) < 0 is ill-formed (12.2.2.3 [over.match.oper] paragraph 8).

There is implementation divergence.

Subclause 11.10.3 [class.spaceship] paragraph 1 seems to prefer an ill-formed program for similar synthesized situations:

[Note 1: A synthesized three-way comparison is ill-formed if overload resolution finds usable candidates that do not otherwise meet the requirements implied by the defined expression. —end note]

Suggested resolution:

Change in 11.10.4 [class.compare.secondary] paragraph 2 as follows:
The operator function with parameters x and y is defined as deleted if

in any overload resolution, the defaulted operator function is not considered as a candidate for the @ operator. Otherwise, the operator function yields x @ y. The defaulted operator function is not considered as a candidate in the overload resolution for the @ operator.




2189. Surrogate call template

Section: 12.2.2.2.3  [over.call.object]     Status: open     Submitter: Jason Merrill     Date: 2015-10-22

Consider:

  template <class T>
  using Fn = void (*)(T);

  struct A
  {
    template <class T>
    operator Fn<T>();
  };

  int main()
  {
    A()(42);
  }
12.2.2.2.3 [over.call.object] describes how conversion functions to pointer/reference to function work in overload resolution, but is silent about conversion function templates. Generalizing the wording there, in this case we could generate a surrogate conversion template
template <class T>
  void /surrogate/ (Fn<T> f, T a) { return f(a); }
which would work as expected. But it seems that implementations don't actually do this currently.


2564. Conversion to function pointer with an explicit object parameter

Section: 12.2.2.2.3  [over.call.object]     Status: open     Submitter: Christof Meerwald     Date: 2022-04-11

Subclause 12.2.2.2.3 [over.call.object] paragraph 2 considers only those conversion funtions matching a particular grammar pattern. This unintendedly excludes conversion functions with an explicit object parameter (and, as a pre-existing defect, noexcept conversion functions):

In addition, for each non-explicit conversion function declared in T of the form
operator conversion-type-id ( ) cv-qualifier-seqopt ref-qualifieropt noexcept-specifieropt attribute-specifier-seqopt ;
where the optional cv-qualifier-seq is the same cv-qualification as, or a greater cv-qualification than, cv, and where conversion-type-id denotes the type “pointer to function of (P1 , . . . , Pn ) returning R”, or the type “reference to pointer to function of (P1 , . . . , Pn ) returning R”, or the type “reference to function of (P1 , . . . , Pn ) returning R”, a surrogate call function with the unique name call-function and having the form
R call-function ( conversion-type-id F, P1 a1 , ... , Pn an ) { return F (a1 , . . . , an ); }
is also considered as a candidate function. Similarly, surrogate call functions are added to the set of candidate functions for each non-explicit conversion function declared in a base class of T provided the function is not hidden within T by another intervening declaration. [ Footnote: ...]

For example, there is implementation divergence in handling this example:

  using fn_t = void();
  struct C {
    operator fn_t * (this C const &);
  };

  void foo(C c) {
    c();
  }



545. User-defined conversions and built-in operator overload resolution

Section: 12.2.2.3  [over.match.oper]     Status: open     Submitter: Steve Clamage     Date: 31 October 2005

Consider the following example:

    class B1 {};
    typedef void (B1::*PB1) (); // memptr to B1

    class B2 {};
    typedef void (B2::*PB2) (); // memptr to B2

    class D1 : public B1, public B2 {};
    typedef void (D1::*PD) (); // memptr to D1

    struct S {
         operator PB1(); // can be converted to PD
    } s;
    struct T {
         operator PB2(); // can be converted to PD
    } t;

    void foo() {
         s == t; // Is this an error?
    }

According to 12.5 [over.built] paragraph 16, there is an operator== for PD (“For every pointer to member type...”), so why wouldn't it be used for this comparison?

Mike Miller: The problem, as I understand it, is that 12.2.2.3 [over.match.oper] paragraph 3, bullet 3, sub-bullet 3 is broader than it was intended to be. It says that candidate built-in operators must “accept operand types to which the given operand or operands can be converted according to 12.2.4.2 [over.best.ics].” 12.2.4.2.3 [over.ics.user] describes user-defined conversions as having a second standard conversion sequence, and there is nothing to restrict that second standard conversion sequence.

My initial thought on addressing this would be to say that user-defined conversion sequences whose second standard conversion sequence contains a pointer conversion or a pointer-to-member conversion are not considered when selecting built-in candidate operator functions. They would still be applicable after the hand-off to Clause 5 (e.g., in bringing the operands to their common type, 7.6.10 [expr.eq], or composite pointer type, 7.6.9 [expr.rel]), just not in constructing the list of built-in candidate operator functions.

I started to suggest restricting the second standard conversion sequence to conversions having Promotion or Exact Match rank, but that would exclude the Boolean conversions, which are needed for !, &&, and ||. (It would have also restricted the floating-integral conversions, though, which might be a good idea. They can't be used implicitly, I think, because there would be an ambiguity among all the promoted integral types; however, none of the compilers I tested even tried those conversions because the errors I got were not ambiguities but things like “floating point operands not allowed for %”.)

Bill Gibbons: I recall seeing this problem before, though possibly not in committee discussions. As written this rule makes the set of candidate functions dependent on what classes have been defined, including classes not otherwise required to have been defined in order for "==" to be meaningful. For templates this implies that the set is dependent on what templates have been instantiated, e.g.

  template<class T> class U : public T { };
  U<B1> u;  // changes the set of candidate functions to include
            // operator==(U<B1>,U<B1>)?

There may be other places where the existence of a class definition, or worse, a template instantiation, changes the semantics of an otherwise valid program (e.g. pointer conversions?) but it seems like something to be avoided.

(See also issue 954.)




1919. Overload resolution for ! with explicit conversion operator

Section: 12.2.2.3  [over.match.oper]     Status: open     Submitter: Johannes Schaub     Date: 2014-04-30

Although the intent is that the ! operator should be usable with an operand that is a class object having an explicit conversion to bool (i.e., its operand is “contextually converted to bool”), the selection of the conversion operator is done via 12.2.2.3 [over.match.oper], 12.2.3 [over.match.viable], and 12.2.4 [over.match.best], which do not make specific allowance for this special characteristic of the ! operator and thus will not select the explicit conversion function.

Notes from the June, 2014 meeting:

CWG noted that this same issue affects && and ||.




2311. Missed case for guaranteed copy elision

Section: 12.2.2.8  [over.match.list]     Status: open     Submitter: Richard Smith     Date: 2016-08-09

Consider:

  struct X {
    X();
  };
  X make();
  X x{make()}; 

We reach 9.4.5 [dcl.init.list] bullet 3.7:

Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution (12.2 [over.match], 12.2.2.8 [over.match.list]).

This means we perform a redundant copy. If T were an aggregate, 9.4.5 [dcl.init.list] bullet 3.2 would avoid the redundant copy:

If T is an aggregate class and the initializer list has a single element of type cv U, where U is T or a class derived from T, the object is initialized from that element (by copy-initialization for copy-list-initialization, or by direct-initialization for direct-list-initialization).

See also issues 2137 and 2327.




2425. Confusing wording for deduction from a type

Section: 12.2.2.9  [over.match.class.deduct]     Status: open     Submitter: Dawn Perchik     Date: 2019-08-06

In 12.2.2.9 [over.match.class.deduct] paragraph 3 we read:

The arguments of a template A are said to be deducible from a type T if, given a class template

  template <typename> class AA;

with a single partial specialization whose template parameter list is that of A and whose template argument list is a specialization of A with the template argument list of A (13.8.3.2 [temp.dep.type]), AA<T> matches the partial specialization.

The relationship between A, AA and its partial specialization, and the argument list of A is not clear. An example would be very helpful here. Also, using a different name than A would help, since A is used in close proximity to this wording to denote an alias template, while this wording applies to both class and alias templates. Finally, there should be a cross-reference to 13.7.6.2 [temp.spec.partial.match] for matching the partial specialization.




2628. Implicit deduction guides should propagate constraints

Section: 12.2.2.9  [over.match.class.deduct]     Status: open     Submitter: Roy Jacobson     Date: 2022-09-11

Consider:

template<class T> concept True = true;

template<class T> struct X {
  template<class U> requires True<T> X(T, U(&)[3]);
};
template<typename T, typename U> X(T, U(&)[3]) -> X<T>;
int arr3[3];
X z(3, arr3);     // #1

According to 12.2.2.9 [over.match.class.deduct] bullet 1.1, the requires-clause of the constructor is not propagated to the function template synthesized for the implicit deduction guide, making the implicit deduction guide ambiguous with the user-provided one. The example ought to be valid.

Possible resolution:

Change in 12.2.2.9 [over.match.class.deduct] bullet 1.1 as follows:




2680. Class template argument deduction for aggregates with designated initializers

Section: 12.2.2.9  [over.match.class.deduct]     Status: open     Submitter: Christof Meerwald     Date: 2023-01-09

Consider:

   template<typename T>
   struct X
   {
    int i{};
    T t;
   };

   X x{ .t = X<int>{ 1, 2 } }; // should deduce X<X<int>> (not X<int>)

and

   template<typename T>
   struct Y
   {
    int i;
    T t;
    int j;
   };

   template<typename T>
   Y(int, T) -> Y<T>;

   Y y{ .t = 'c', .j = 2 }; // should be ill-formed

Class template argument deduction does not limit deduction to the aggregate deduction candidate when a designated initializer list is used.

Suggested resolution:

  1. Change in 12.2.2.9 [over.match.class.deduct] paragraph 1 as follows:

    ... If the initializer has a designated-initializer-list, any guide that is not an aggregate deduction candidate is not viable; for an aggregate deduction candidate, the designated-initializer-list is replaced by an initializer-list with the same elements. In addition, if C is defined and inherits constructors (9.9 [namespace.udecl]) from a direct base class ...
  2. Change in 12.2.2.9 [over.match.class.deduct] bullet 3.5 as follows:

    • ...
    • If f was generated from a deduction-guide (13.7.2.3 [temp.deduct.guide]), then f' is considered to be so as well.
    • If f was generated from an aggregate deduction guide, then f' is considered to be so as well.



2679. Implicit conversion sequence with a null pointer constant

Section: 12.2.4.2.1  [over.best.ics.general]     Status: open     Submitter: Lénárd Szolnoki     Date: 2023-01-07

Subclause 12.2.4.2.1 [over.best.ics.general] paragraph 1, as modified by issue 2525, claims that only the type, cv-qualification, and value category of the argument are relevant in forming an implicit conversion sequence. This is inaccurate for the following situations:




2169. Narrowing conversions and overload resolution

Section: 12.2.4.2.6  [over.ics.list]     Status: open     Submitter: David Krauss     Date: 2015-08-26

Current implementations ignore narrowing conversions during overload resolution, emitting a diagnostic if calling the selected function would involve narrowing. For example:

  struct s { long m };
  struct ss { short m; };

  void f( ss );
  void f( s );
  void g() {
    f({ 1000000 }); // Ambiguous in spite of narrowing for f(ss)
  }

However, the current wording of 12.2.4.2.6 [over.ics.list] paragraph 7 says,

Otherwise, if the parameter has an aggregate type which can be initialized from the initializer list according to the rules for aggregate initialization (9.4.2 [dcl.init.aggr]), the implicit conversion sequence is a user-defined conversion sequence with the second standard conversion sequence an identity conversion.

In the example above, ss cannot be initialized from { 1000000 } because of the narrowing conversion, so presumably f(ss) should not be considered. If this is not the intended outcome, paragraph 7 should be restated in terms of having an implicit conversion sequence, as in, e.g., bullet 9.1, instead of a valid initialization.

Rationale (March, 2016):

This is a question of language design and thus more suited to consideration by EWG.

EWG (January, 2021):

Adjust the standard to follow existing implementations. See vote.

Additional notes (March, 2023)

A narrowing conversion is but one of the many circumstances that could make a conversion ill-formed despite the existence of an implicit conversion sequence. Other examples (with implementation divergence) are invoking a private or deleted function, binding a non-const reference to a bit-field, invoking an explicit constructor in list-initialization, invoking a consteval constructor with unsuitable arguments, or initializing an aggregate with incorrectly-ordered designated initializers. A resolution of this issue should thus not be focused on narrow conversions.




1459. Reference-binding tiebreakers in overload resolution

Section: 12.2.4.3  [over.ics.rank]     Status: open     Submitter: Jason Merrill     Date: 2012-02-07

Both paragraph 3 and paragraph 4 of 12.2.4.3 [over.ics.rank] have overload resolution tiebreakers for reference binding. It might be possible to merge those into a single treatment.




1789. Array reference vs array decay in overload resolution

Section: 12.2.4.3  [over.ics.rank]     Status: open     Submitter: Faisal Vali     Date: 2013-10-01

The current rules make an example like

  template<class T, size_t N> void foo(T (&)[N]);
  template<class T> void foo(T *t);

  int arr[3]{1, 2, 3};
  foo(arr);

ambiguous, even though the first is an identity match and the second requires an lvalue transformation. Is this desirable?

Proposed resolution (June, 2021):

Add the following as a new bullet following 12.2.4.3 [over.ics.rank] bullet 3.2.6:

Two implicit conversion sequences of the same form are indistinguishable conversion sequences unless one of the following rules applies:

CWG 2023-02-06

The proposed resolution prefers the T& overload over the T* overload in the example below, which is undesirable. The wording needs to be amended to limit the tiebreaker to situations where the array declarator appears in the signature.

template<class T>
int f(T&);
template<class T>
int f(T*);
int x[5];
int z = f(x);



2337. Incorrect implication of logic ladder for conversion sequence tiebreakers

Section: 12.2.4.3  [over.ics.rank]     Status: open     Submitter: Richard Smith     Date: 2017-03-02

The bulleted list of 12.2.4.3 [over.ics.rank] paragraph 3 consists of a logic ladder of the form “A is better than B if [some predicate relating A to B], or, if not that, ...” For example, bullet 3.1 says,

The intent is not to fall into the array case if L2 converts to std::initializer_list<X> and L1 does not — i.e., the inverse predicate holds — but that intent is not well reflected in the actual wording.




2572. Address of overloaded function with no target

Section: 12.3  [over.over]     Status: open     Submitter: Jason Merrill     Date: 2022-04-26

Consider:

  template <class T> T f(T);   // #1
  template <class T> T* f(T*); // #2
  auto p = &f<int>;

Accoring to 12.3 [over.over] paragraph 3 and 12.3 [over.over] paragraph 5:

The specialization, if any, generated by template argument deduction (13.10.4 [temp.over], 13.10.3.3 [temp.deduct.funcaddr], 13.10.2 [temp.arg.explicit]) for each function template named is added to the set of selected functions considered.

[...]

Any given function template specialization F1 is eliminated if the set contains a second function template specialization whose function template is more specialized than the function template of F1 according to the partial ordering rules of 13.7.7.3 [temp.func.order]. After such eliminations, if any, there shall remain exactly one selected function.

Major implementations reject the example as ambiguous, yet the wording specifies to unambiguously choose #2.

Suggested resolution:

Change in 12.3 [over.over] paragraph 5 as follows:

Any given function template specialization F1 is eliminated if the set contains a second function template specialization whose function template is more specialized better than the function template of F1. If there is no target, a function template is better than another if it is more constrained than the other; otherwise a function template is better than another if it is more specialized than the other according to the partial ordering rules of 13.7.7.3 [temp.func.order]. After such eliminations, if any, there shall remain exactly one selected function.



1549. Overloaded comma operator with void operand

Section: 12.4.3  [over.binary]     Status: open     Submitter: Nikolay Ivchenkov     Date: 2012-09-04

Even though a function cannot take a parameter of type void, the current rules for overload resolution require consideration of overloaded operators when one operand has a user-defined or enumeration type and the other has type void. This can result in side effects and possibly errors, for example:

  template <class T> struct A {
    T t;
    typedef T type;
  };

  struct X {
    typedef A<void> type;
  };

  template <class T> void operator ,(typename T::type::type, T) {}

  int main() {
    X(), void(); // OK
    void(), X(); // error: A<void> is instantiated with a field of
                 // type void
  }



260. User-defined conversions and built-in operator=

Section: 12.5  [over.built]     Status: open     Submitter: Scott Douglas     Date: 4 Nov 2000

According to the Standard (although not implemented this way in most implementations), the following code exhibits non-intuitive behavior:

  struct T {
    operator short() const;
    operator int() const;
  };

  short s;

  void f(const T& t) {
    s = t;  // surprisingly calls T::operator int() const
  }

The reason for this choice is 12.5 [over.built] paragraph 18:

For every triple (L, VQ, R), where L is an arithmetic type, VQ is either volatile or empty, and R is a promoted arithmetic type, there exist candidate operator functions of the form

Because R is a "promoted arithmetic type," the second argument to the built-in assignment operator is int, causing the unexpected choice of conversion function.

Suggested resolution: Provide built-in assignment operators for the unpromoted arithmetic types.

Related to the preceding, but not resolved by the suggested resolution, is the following problem. Given:

    struct T {
	 operator int() const;
	 operator double() const;
    };

I believe the standard requires the following assignment to be ambiguous (even though I expect that would surprise the user):

    double x;
    void f(const T& t) { x = t; }

The problem is that both of these built-in operator=()s exist (12.5 [over.built] paragraph 18):

    double& operator=(double&, int);
    double& operator=(double&, double);

Both are an exact match on the first argument and a user conversion on the second. There is no rule that says one is a better match than the other.

The compilers that I have tried (even in their strictest setting) do not give a peep. I think they are not following the standard. They pick double& operator=(double&, double) and use T::operator double() const.

I hesitate to suggest changes to overload resolution, but a possible resolution might be to introduce a rule that, for built-in operator= only, also considers the conversion sequence from the second to the first type. This would also resolve the earlier question.

It would still leave x += t etc. ambiguous -- which might be the desired behavior and is the current behavior of some compilers.

Notes from the 04/01 meeting:

The difference between initialization and assignment is disturbing. On the other hand, promotion is ubiquitous in the language, and this is the beginning of a very slippery slope (as the second report above demonstrates).

Additional note (August, 2010):

See issue 507 for a similar example involving comparison operators.




954. Overload resolution of conversion operator templates with built-in types

Section: 12.5  [over.built]     Status: open     Submitter: Steve Clamage     Date: 19 August, 2009

Consider the following example:

    struct NullClass {
        template<typename T> operator T () { return 0 ; }
    };

    int main() {
        NullClass n;
        n==5;        // #1
        return 0;
    }

The comparison at #1 is, according to the current Standard, ambiguous. According to 12.5 [over.built] paragraph 12, the candidates for operator==(L, R) include functions “for every pair of promoted arithmetic types,” so L could be either int or long, and the conversion operator template will provide an exact match for either.

Some implementations unambiguously choose the int candidate. Perhaps the overload resolution rules could be tweaked to prefer candidates in which L and R are the same type?

(See also issue 545.)




1620. User-defined literals and extended integer types

Section: 12.6  [over.literal]     Status: open     Submitter: Jason Merrill     Date: 2013-02-12

Although numeric literals can have extended integer types, user-defined literal operators cannot have a parameter of an extended integer type. This seems like an oversight.




2617. Default template arguments for template members of non-template classes

Section: 13.2  [temp.param]     Status: open     Submitter: Mike Miller     Date: 2022-08-22

Consider:

struct S {
  template<typename> void f();
};

template<typename = int> void S::f() { }   // ok?

There is implementation divergence in the treatment of this example. The relevant wording appears to be 13.2 [temp.param] paragraph 12:

A default template-argument shall not be specified in the template-parameter-lists of the definition of a member of a class template that appears outside of the member's class.

However, the example above deals with a member of an ordinary class, not a class template, but it is not clear why there should be a difference between a member template of a class template and a member template of a non-template class.

Alternatively, it is not clear why the example above should be treated differently from a non-member function template, e.g.,

template<typename> void f();
template<typename = int> void f() { }

which is explicitly permitted.




579. What is a “nested” > or >>?

Section: 13.3  [temp.names]     Status: open     Submitter: Daveed Vandevoorde     Date: 11 May 2006

The Standard does not normatively define which > and >> tokens are to be taken as closing a template-argument-list; instead, 13.3 [temp.names] paragraph 3 uses the undefined and imprecise term “non-nested:”

When parsing a template-id, the first non-nested > is taken as the end of the template-argument-list rather than a greater-than operator. Similarly, the first non-nested >> is treated as two consecutive but distinct > tokens, the first of which is taken as the end of the template-argument-list and completes the template-id.

The (non-normative) footnote clarifies that

A > that encloses the type-id of a dynamic_cast, static_cast, reinterpret_cast or const_cast, or which encloses the template-arguments of a subsequent template-id, is considered nested for the purpose of this description.

Aside from the questionable wording of this footnote (e.g., in what sense does a single terminating character “enclose” anything, and is a nested template-id “subsequent?”) and the fact that it is non-normative, it does not provide a complete definition of what “nesting” is intended to mean. For example, is the first > in this putative template-id “nested” or not?

    X<a ? b > c : d>

Additional note (January, 2014):

A similar problem exists for an operator> template:

  struct S;
  template<void (*)(S, S)> struct X {};
  void operator>(S, S);
  X<operator> > x;

Somehow the specification must be written to avoid taking the > token in the operator name as the end of the template argument list for X.




440. Allow implicit pointer-to-member conversion on nontype template argument

Section: 13.4  [temp.arg]     Status: open     Submitter: David Abrahams     Date: 13 Nov 2003

None of my compilers accept this, which surprised me a little. Is the base-to-derived member function conversion considered to be a runtime-only thing?

  template <class D>
  struct B
  {
      template <class X> void f(X) {}
      template <class X, void (D::*)(X) = &B<D>::f<X> >
      struct row {};
  };
  struct D : B<D>
  {
      void g(int);
      row<int,&D::g> r1;
      row<char*> r2;
  };

John Spicer: This is not among the permitted conversions listed in 14.3.

I'm not sure there is a terribly good reason for that. Some of the template argument rules for external entities were made conservatively because of concerns about issues of mangling template argument names.

David Abrahams: I'd really like to see that restriction loosened. It is a serious inconvenience because there appears to be no way to supply a usable default in this case. Zero would be an OK default if I could use the function pointer's equality to zero as a compile-time switch to choose an empty function implementation:

  template <bool x> struct tag {};

  template <class D>
  struct B
  {
      template <class X> void f(X) {}

      template <class X, void (D::*pmf)(X) = 0 >
      struct row {
          void h() { h(tag<(pmf == 0)>(), pmf); }
          void h(tag<1>, ...) {}
          void h(tag<0>, void (D::*q)(X)) { /*something*/}
      };
  };

  struct D : B<D>
  {
      void g(int);
      row<int,&D::g> r1;
      row<char*> r2;
  };

But there appears to be no way to get that effect either. The result is that you end up doing something like:

      template <class X, void (D::*pmf)(X) = 0 >
      struct row {
          void h() { if (pmf) /*something*/ }
      };

which invariably makes compilers warn that you're switching on a constant expression.

Additional notes (February, 2023)

Even the following simple case fails on all major implementations:

  struct B { void f(); };
  struct D : B {};

  template<void (D::*)()>
  int g();

  int x = f<&D::g>();

Subclause 7.7 [expr.const] paragraph 11 is unambiguous that the code is ill-formed, but that seems unfortunate.




2105. When do the arguments for a parameter pack end?

Section: 13.4  [temp.arg]     Status: open     Submitter: Hubert Tong     Date: 2015-03-17

There does not appear to be a clear statement in the Standard that the first template parameter pack in a template parameter list corresponds to all remaining arguments in the template argument list. For example:

  template <int> struct A;

  template <int ...N, typename T> void foo(A<N> *..., T);
  void bar() {
   foo<0>(0, 0);      // okay: N consists of one template parameter, 0. T is deduced to int
   foo<0, int>(0, 0); // error: int does not match the form of the corresponding parameter N
  }

See also issue 2055.

Notes from the February, 2016 meeting:

The comments in the example reflect the intent.




2686. Pack expansion into a non-pack parameter of a concept

Section: 13.5.2  [temp.constr.constr]     Status: open     Submitter: Michał Dominiak     Date: 2021-10-15

Consider:

  template<typename T>  concept A = true;
  template<typename... T> concept B = A<T...>;

Is this valid? MSVC accepts; gcc and clang reject. See also issue 1430 for the equivalent question for alias templates.




2589. Context of access checks during constraint satisfaction checking

Section: 13.5.2.3  [temp.constr.atomic]     Status: open     Submitter: Jason Merrill     Date: 2019-10-02

Consider:

  template<class T> concept ctible = requires { T(); };

  class A {
    template <class T> friend struct B;
    A();
  };

  template <class T> struct B;
  template <ctible T> struct B<T> { T t; };
  B<A> b;  // #1

  template <class T> struct C { };
  template <ctible T> struct C<T> { T t; };
  C<A> c;  // #2

Should the context of instantiation be considered for satisfaction checking? If satisfaction checking were always performed in an unrelated context, neither partial specialization is used, and #1 would be ill-formed (because B is incomplete), but #2 would be well-formed. If the satisfaction checking were performed in the context of the constrained declaration, #1 would be well-formed and #2 would be ill-formed, no diagnostic required, because the validity of A() is different in that context. That rule, however, could also consider the context, in which case #2 would also be well-formed.

The decision affects the amount of caching that an implementation can perform.

Subclause 13.5.2.3 [temp.constr.atomic] paragraph 3 should be clarified one way or another:

To determine if an atomic constraint is satisfied, the parameter mapping and template arguments are first substituted into its expression. If substitution results in an invalid type or expression, the constraint is not satisfied. Otherwise, the lvalue-to-rvalue conversion (7.3.2 [conv.lval]) is performed if necessary, and E shall be a constant expression of type bool. The constraint is satisfied if and only if evaluation of E results in true. If, at different points in the program, the satisfaction result is different for identical atomic constraints and template arguments, the program is ill-formed, no diagnostic required.



2697. Deduction guides using abbreviated function syntax

Section: 13.7.2.3  [temp.deduct.guide]     Status: open     Submitter: CWG     Date: 2023-02-11     Liaison: EWG

It is unclear whether deduction guides can be expressed using abbreviated function syntax. Subclause 13.7.2.3 [temp.deduct.guide] paragraph 3 refers to the restrictions of a function's parameter-declaration-clause:

The same restrictions apply to the parameter-declaration-clause of a deduction guide as in a function declaration (9.3.4.6 [dcl.fct]). ...

However, that subclause is silent on the meaning of abbreviated function syntax when used for deduction guides. Furthermore, 9.3.4.6 [dcl.fct] paragraph 22 explicitly restricts the definition to function templates, which deduction guides are not:

An abbreviated function template is a function declaration that has one or more generic parameter type placeholders (9.2.9.6 [dcl.spec.auto]). ...

Arguably, the lack of template parameter names in abbreviated function syntax makes it less suitable to specifiy deduction guides.

CWG 2023-02-11

CWG solicits input from EWG whether abbreviated function syntax is intended to be used for deduction guides. See cplusplus/papers#1465.




2707. Deduction guides cannot have a trailing requires-clause

Section: 13.7.2.3  [temp.deduct.guide]     Status: open     Submitter: Richard Smith     Date: 2020-02-26

The grammar for deduction-guide does not, but should, allow a trailing requires-clause:

deduction-guide:
   explicit-specifieropt template-name ( parameter-declaration-clause ) -> simple-template-id ;

Suggested resolution:

Change the grammar in 13.7.2.3 [temp.deduct.guide] paragraph 1 as follows:

deduction-guide:
   explicit-specifieropt template-name ( parameter-declaration-clause ) requires-clauseopt -> simple-template-id ;



1918. friend templates with dependent scopes

Section: 13.7.5  [temp.friend]     Status: open     Submitter: Richard Smith     Date: 2014-04-27

It is not clear what should happen for an example like:

  template<typename T> struct A {
    class B {
      class C {};
    };
  };
  class X {
    static int x;
    template <typename T> friend class A<T>::B::C;
  };
  template<> struct A<int> {
    typedef struct Q B;
  };
  struct Q {
    class C {
      int f() { return X::x; }
    };
  };

It appears that the friend template matches Q::C, because that class is also A<int>::B::C, but neither GCC nor EDG allow this code (saying X::x is inaccessible). (Clang doesn't support friend template declarations with a dependent scope.)

A strict reading of 13.7.5 [temp.friend] paragraph 5 might suggest that the friend declaration itself is ill-formed, because it does not declare a member of a class template, but I can't find any compiler that implements template friends that way.




1945. Friend declarations naming members of class templates in non-templates

Section: 13.7.5  [temp.friend]     Status: open     Submitter: Richard Smith     Date: 2014-06-19

During the discussion of issue 1918, it was decided that the last part of the issue should be split off into a separate issue. According to 13.7.5 [temp.friend] paragraph 5,

A member of a class template may be declared to be a friend of a non-template class.

Does this make the example from issue 1918,

  template<typename T> struct A {
    class B {
      class C {};
    };
  };
  class X {
    static int x;
    template <typename T> friend class A<T>::B::C;
  };
  template<> struct A<int> {
    typedef struct Q B;
  };
  struct Q {
    class C {
      int f() { return X::x; }
    };
  };

ill-formed because the friend declaration does not refer to a member of a class template? This does not appear to be the interpretation chosen by most implementations.




2118. Stateful metaprogramming via friend injection

Section: 13.7.5  [temp.friend]     Status: open     Submitter: Richard Smith     Date: 2015-04-27

Defining a friend function in a template, then referencing that function later provides a means of capturing and retrieving metaprogramming state. This technique is arcane and should be made ill-formed.

Notes from the May, 2015 meeting:

CWG agreed that such techniques should be ill-formed, although the mechanism for prohibiting them is as yet undetermined.




708. Partial specialization of member templates of class templates

Section: 13.7.6  [temp.spec.partial]     Status: open     Submitter: James Widman     Date: 8 Aug, 2008

The Standard does not appear to specify clearly the effect of a partial specialization of a member template of a class template. For example:

    template<class T> struct B {
         template<class U> struct A { // #1
             void h() {}
         };
         template<class U> struct A<U*> {  // #2
             void f() {}
         };
    };

    template<> template<class U> struct B<int>::A { // #3
         void g() {}
    };

    void q(B<int>::A<char*>& p) {
         p.f();  // #4
    }

The explicit specialization at #3 replaces the primary member template #1 of B<int>; however, it is not clear whether the partial specialization #2 should be considered to apply to the explicitly-specialized member template of A<int> (thus allowing the call to p.f() at #4) or whether the partial specialization will be used only for specializations of B that are implicitly instantiated (meaning that #4 could call p.g() but not p.f()).




2173. Partial specialization with non-deduced contexts

Section: 13.7.6  [temp.spec.partial]     Status: open     Submitter: Mike Miller     Date: 2015-09-14

During the discussion of issue 1315, it was observed that the example

  template <int I, int J> struct B {};
  template <int I> struct B<I, I*2> {};

is ill-formed because the deduction succeeds in both directions. This seems surprising. It was suggested that perhaps a non-deduced context should be considered more specialized than a deduced context.




310. Can function templates differing only in parameter cv-qualifiers be overloaded?

Section: 13.7.7.2  [temp.over.link]     Status: open     Submitter: Andrei Iltchenko     Date: 29 Aug 2001

I get the following error diagnostic [from the EDG front end]:

line 8: error: function template "example<T>::foo<R,A>(A)" has
          already been declared
     R  foo(const A);
        ^
when compiling this piece of code:
struct  example  {
   template<class R, class A>   // 1-st member template
   R  foo(A);
   template<class R, class A>   // 2-nd member template
   const R  foo(A&);
   template<class R, class A>   // 3-d  member template
   R  foo(const A);
};

/*template<> template<>
int  example<char>::foo(int&);*/


int  main()
{
   int  (example<char>::* pf)(int&) =
      &example<char>::foo;
}

The implementation complains that

   template<class R, class A>   // 1-st member template
   R  foo(A);
   template<class R, class A>   // 3-d  member template
   R  foo(const A);
cannot be overloaded and I don't see any reason for it since it is function template specializations that are treated like ordinary non-template functions, meaning that the transformation of a parameter-declaration-clause into the corresponding parameter-type-list is applied to specializations (when determining its type) and not to function templates.

What makes me think so is the contents of 13.7.7.2 [temp.over.link] and the following sentence from 13.10.3.2 [temp.deduct.call] "If P is a cv-qualified type, the top level cv-qualifiers of P are ignored for type deduction". If the transformation was to be applied to function templates, then there would be no reason for having that sentence in 13.10.3.2 [temp.deduct.call].

13.10.3.3 [temp.deduct.funcaddr], which my example is based upon, says nothing about ignoring the top level cv-qualifiers of the function parameters of the function template whose address is being taken.

As a result, I expect that template argument deduction will fail for the 2-nd and 3-d member templates and the 1-st one will be used for the instantiation of the specialization.




2584. Equivalent types in function template declarations

Section: 13.7.7.2  [temp.over.link]     Status: open     Submitter: Jim X     Date: 2022-04-08

According to 6.4.1 [basic.scope.scope] paragraph 4:

Two declarations correspond if they (re)introduce the same name, both declare constructors, or both declare destructors, unless

Assuming that two non-object-parameter-type-lists are equivalent if they have the same length and corresponding types are equivalent, the question remains when two (possibly dependent) types are equivalent. Subclause 13.7.7.2 [temp.over.link] should provide an answer, but only covers expressions appearing in such types (paragraph 5):

Two expressions involving template parameters are considered equivalent if...

For example, the standard should specify whether these declarations correspond:

  template<class T> T   f();
  template<class T> T&& f();

  template<class T, class U> void g(decltype(T::foo));
  template<class T, class U> void g(decltype(U::foo));

A related issue is the determination whether two names are the same; for example:

  struct A {
    template<class T>
    operator T();

    template<class T>
    operator T&&();
  };

The latter issue could probably be fixed by amending 11.4.8.3 [class.conv.fct] to state that two conversion-function-ids are the same if their conversion-type-ids denote equivalent types, with a cross-reference to 13.7.7.2 [temp.over.link].




402. More on partial ordering of function templates

Section: 13.7.7.3  [temp.func.order]     Status: open     Submitter: Nathan Sidwell     Date: 7 Apr 2003

This was split off from issue 214 at the April 2003 meeting.

Nathan Sidwell: John Spicer's proposed resolution does not make the following well-formed.

  template <typename T> int Foo (T const *) {return 1;} //#1
  template <unsigned I> int Foo (char const (&)[I]) {return 2;} //#2

  int main ()
  {
    return Foo ("a") != 2;
  }

Both #1 and #2 can deduce the "a" argument, #1 deduces T as char and #2 deduces I as 2. However, neither is more specialized because the proposed rules do not have any array to pointer decay.

#1 is only deduceable because of the rules in 13.10.3.2 [temp.deduct.call] paragraph 2 that decay array and function type arguments when the template parameter is not a reference. Given that such behaviour happens in deduction, I believe there should be equivalent behaviour during partial ordering. #2 should be resolved as more specialized as #1. The following alteration to the proposed resolution of DR214 will do that.

Insert before,

the following

For the example above, this change results in deducing 'T const *' against 'char const *' in one direction (which succeeds), and 'char [I]' against 'T const *' in the other (which fails).

John Spicer: I don't consider this a shortcoming of my proposed wording, as I don't think this is part of the current rules. In other words, the resolution of 214 might make it clearer how this case is handled (i.e., clearer that it is not allowed), but I don't believe it represents a change in the language.

I'm not necessarily opposed to such a change, but I think it should be reviewed by the core group as a related change and not a defect in the proposed resolution to 214.

Notes from the October 2003 meeting:

There was some sentiment that it would be desirable to have this case ordered, but we don't think it's worth spending the time to work on it now. If we look at some larger partial ordering changes at some point, we will consider this again.




1157. Partial ordering of function templates is still underspecified

Section: 13.7.7.3  [temp.func.order]     Status: open     Submitter: CA     Date: 2010-08-03

N3092 comment CA 7

13.7.7.3 [temp.func.order] paragraph 3 says,

To produce the transformed template, for each type, non-type, or template template parameter (including template parameter packs (13.7.4 [temp.variadic]) thereof) synthesize a unique type, value, or class template respectively and substitute it for each occurrence of that parameter in the function type of the template.

The characteristics of the synthesized entities and how they are determined is not specified. For example, members of a dependent type referred to in non-deduced contexts are not specified to exist, even though the transformed function type would be invalid in their absence.

Example 1:

  template<typename T, typename U> struct A;
  template<typename T> void foo(A<T, typename T::u> *) { } // #1
    // synthetic T1 has member T1::u
  template <typename T> void foo(A<T, typename T::u::v> *) { } // #2
    // synthetic T2 has member T2::u and member T2::u::v
    // T in #1 deduces to synthetic T2 in partial ordering;
    // deduced A for the parameter is A<T2, T2::u> * --this is not necessarily compatible
    // with A<T2, T2::u::v> * and it does not need to be. See Note 1. The effect is that
    // (in the call below) the compatibility of B::u and B::u::v is respected.
    // T in #2 cannot be successfully deduced in partial ordering from A<T1, T1::u> *;
    // invalid type T1::u::v will be formed when T1 is substituted into non-deduced contexts.
  struct B {
    struct u { typedef u v; };
  };
  int main() {
    foo((A<B, B::u> *)0); // calls #2
  }

Note 1: Template argument deduction is an attempt to match a P and a deduced A; however, template argument deduction is not specified to fail if the P and the deduced A are incompatible. This may occur in the presence of non-deduced contexts. Notwithstanding the parenthetical statement in 13.10.3.5 [temp.deduct.partial] paragraph 9, template argument deduction may succeed in determining a template argument for every template parameter while producing a deduced A that is not compatible with the corresponding P.

Example 2:

  template <typename T, typename U, typename V> struct A;
  template <typename T>
    void foo(A<T, struct T::u, struct T::u::u> *); // #2.1
      // synthetic T1 has member non-union class T1::u
  template <typename T, typename U>
    void foo(A<T, U , U> *); // #2.2
      // synthetic T2 and U2 has no required properties
      // T in #2.1 cannot be deduced in partial ordering from A<T2, U2, U2> *;
      // invalid types T2::u and T2::u::u will be formed when T2 is substituted in nondeduced contexts.
      // T and U in #2.2 deduces to, respectively, T1 and T1::u from A<T1, T1::u, struct
T1::u::u> * unless
      // struct T1::u::u does not refer to the injected-class-name of the class T1::u (if that is possible).
  struct B {
    struct u { };
  };
  int main() {
    foo((A<B, B::u, struct B::u::u> *)0); // calls #2.1
  }

It is, however, unclear to what extent an implementation will have to go to determine these minimal properties.




2160. Issues with partial ordering

Section: 13.7.7.3  [temp.func.order]     Status: open     Submitter: Richard Smith     Date: 2015-07-16

(From this editorial issue.)

Consistency of deduced values

  template <typename T> void foo(T, T); // (1)
  template <typename T, typename U> void foo(T, U); // (2)

13.10.3.6 [temp.deduct.type] paragraph 2 makes it clear that there must be exactly one set of deduced values for the Ps. But there is no such statement in the partial ordering rule. The algorithm described only does pairwise P/A matching, so a synthesized call from (2) to (1) via foo(U{}, V{}) could succeed in deduction. Both gcc and clang agree that (1) is more specialized.

Type Synthesis Template Instantiation

  template <typename T>
  struct identity { using type = T; };

  template<typename T> void bar(T, T ); // (1) 
  template<typename T> void bar(T, typename identity<T>::type ); // (2)

Here, if synthesized for (2) Unique2 and typename identity<Unique2>::type == Unique2 , then type deduction would succeed in both directions and the call bar(0,0) would be ambiguous. However, it seems that both compilers instead simply treat typename identity<Unique2>::type as Unique2_b, thus making template deduction from (2) to (1) fail (based on the implied missing Consistency rule).

Non-deduced Context Omission

This is the same as the previous example, except now define

  template <typename T> struct identity;
  template <> struct identity<int> { using type = int; };

With no template instantiation during synthesis and consistency, the (2) ==> (1) deduction fails. But if we consider the (1) ==> (2) call, we'd match T against Unique1 and then have the non-deduced context typename identity<Unique1>::type to match against Unique1, but that would be a substitution failure. It seems that the approach taken by gcc and clang (both of which prefer (1) here) is to ignore the non-deduced context argument, as long as that parameter type is deduced from a different template parameter type that did get matched.

Notes from the February, 2016 meeting:

None of these examples appears to reflect a defect in the current wording; in particular, the second and third examples involve a dependent type and there could be a later specialization of identity, so it's impossible to reason about those cases in the template definition context. The issue will be left open to allow for possible clarification of the intent of the wording.




1430. Pack expansion into fixed alias template parameter list

Section: 13.7.8  [temp.alias]     Status: open     Submitter: Jason Merrill     Date: 2011-12-13

Originally, a pack expansion could not expand into a fixed-length template parameter list, but this was changed in N2555. This works fine for most templates, but causes issues with alias templates.

In most cases, an alias template is transparent; when it's used in a template we can just substitute in the dependent template arguments. But this doesn't work if the template-id uses a pack expansion for non-variadic parameters. For example:

    template<class T, class U, class V>
    struct S {};

    template<class T, class V>
    using A = S<T, int, V>;

    template<class... Ts>
    void foo(A<Ts...>);

There is no way to express A<Ts...> in terms of S, so we need to hold onto the A until we have the Ts to substitute in, and therefore it needs to be handled in mangling.

Currently, EDG and Clang reject this testcase, complaining about too few template arguments for A. G++ did as well, but I thought that was a bug. However, on the ABI list John Spicer argued that it should be rejected.

(See also issue 1558.)

Notes from the October, 2012 meeting:

The consensus of CWG was that this usage should be prohibited, disallowing use of an alias template when a dependent argument can't simply be substituted directly into the type-id.

Additional note, April, 2013:

For another example, consider:

  template<class... x> class list{};
  template<class a, class... b> using tail=list<b...>;
  template <class...T> void f(tail<T...>);

  int main() {
    f<int,int>({});
  }

There is implementation variance in the handling of this example.

CWG 2022-11-11

There is no more implementation divergence; all known implementations reject the example.




1257. Instantiation via non-dependent references in uninstantiated templates

Section: 13.8  [temp.res]     Status: open     Submitter: Johannes Schaub     Date: 2011-03-09

The Standard does not appear to specify whether a non-dependent reference to a template specialization in a template definition that is never instantiated causes the implicit instantiation of the referenced specialization.




2067. Generated variadic templates requiring empty pack

Section: 13.8  [temp.res]     Status: open     Submitter: Richard Smith     Date: 2015-01-09

According to 13.8 [temp.res] paragraph 8,

If every valid specialization of a variadic template requires an empty template parameter pack, the template is ill-formed, no diagnostic required.

I'm inclined to think that this rule should only apply to code the user wrote. That is, if every valid instantiation of an entity (that was not itself instantiated) requires at least one of the enclosing template argument lists to include an empty template argument pack, then the program is ill-formed (no diagnostic required).




186. Name hiding and template template-parameters

Section: 13.8.2  [temp.local]     Status: open     Submitter: John Spicer     Date: 11 Nov 1999

The standard prohibits a class template from having the same name as one of its template parameters (13.8.2 [temp.local] paragraph 4) . This prohibits

    template <class X> class X;
for the reason that the template name would hide the parameter, and such hiding is in general prohibited.

Presumably, we should also prohibit

    template <template <class T> class T> struct A;
for the same reason.


459. Hiding of template parameters by base class members

Section: 13.8.2  [temp.local]     Status: open     Submitter: Daveed Vandevoorde     Date: 2 Feb 2004

Currently, member of nondependent base classes hide references to template parameters in the definition of a derived class template.

Consider the following example:

   class B {
      typedef void *It;    // (1)
      // ...
    };

    class M: B {};

    template<typename> X {};

    template<typename It> struct S   // (2)
        : M, X<It> {   // (3)
      S(It, It);   // (4)
      // ...
    };

As the C++ language currently stands, the name "It" in line (3) refers to the template parameter declared in line (2), but the name "It" in line (4) refers to the typedef in the private base class (declared in line (1)).

This situation is both unintuitive and a hindrance to sound software engineering. (See also the Usenet discussion at http://tinyurl.com/32q8d .) Among other things, it implies that the private section of a base class may change the meaning of the derived class, and (unlike other cases where such things happen) there is no way for the writer of the derived class to defend the code against such intrusion (e.g., by using a qualified name).

Changing this can break code that is valid today. However, such code would have to:

  1. name a template parameter and not use it after the opening brace, and
  2. use that same name to access a base-class name within the braces.
I personally have no qualms breaking such a program.

It has been suggested to make situations like these ill-formed. That solution is unattractive however because it still leaves the writer of a derived class template without defense against accidental name conflicts with base members. (Although at least the problem would be guaranteed to be caught at compile time.) Instead, since just about everyone's intuition agrees, I would like to see the rules changed to make class template parameters hide members of the same name in a base class.

See also issue 458.

Notes from the March 2004 meeting:

We have some sympathy for a change, but the current rules fall straightforwardly out of the lookup rules, so they're not “wrong.” Making private members invisible also would solve this problem. We'd be willing to look at a paper proposing that.

Additional discussion (April, 2005):

John Spicer: Base class members are more-or-less treated as members of the class, [so] it is only natural that the base [member] would hide the template parameter.

Daveed Vandevoorde: Are base class members really “more or less” members of the class from a lookup perspective? After all, derived class members can hide base class members of the same name. So there is some pretty definite boundary between those two sets of names. IMO, the template parameters should either sit between those two sets, or they should (for lookup purposes) be treated as members of the class they parameterize (I cannot think of a practical difference between those two formulations).

John Spicer: How is [hiding template parameters] different from the fact that namespace members can be hidden by private parts of a base class? The addition of int C to N::A breaks the code in namespace M in this example:

    namespace N {
       class A {
    private:
         int C;
       };
    }

    namespace M {
       typedef int C;
       class B : public N::A {
         void f() {
             C c;
         }
       };
    }

Daveed Vandevoorde: C++ has a mechanism in place to handle such situations: qualified names. There is no such mechanism in place for template parameters.

Nathan Myers: What I see as obviously incorrect ... is simply that a name defined right where I can see it, and directly attached to the textual scope of B's class body, is ignored in favor of something found in some other file. I don't care that C1 is defined in A, I have a C1 right here that I have chosen to use. If I want A::C1, I can say so.

I doubt you'll find any regular C++ coder who doesn't find the standard behavior bizarre. If the meaning of any code is changed by fixing this behavior, the overwhelming majority of cases will be mysterious bugs magically fixed.

John Spicer: I have not heard complaints that this is actually a cause of problems in real user code. Where is the evidence that the status quo is actually causing problems?

In this example, the T2 that is found is the one from the base class. I would argue that this is natural because base class members are found as part of the lookup in class B:

    struct A {
             typedef int T2;
    };
    template <class T2> struct B : public A {
             typedef int T1;
             T1 t1;
             T2 t2;
    };

This rule that base class members hide template parameters was formalized about a dozen years ago because it fell out of the principle that base class members should be found at the same stage of lookup as derived class members, and that to do otherwise would be surprising.

Gabriel Dos Reis: The bottom line is that:

  1. the proposed change is a silent change of meaning;
  2. the proposed change does not make the language any more regular; the current behavior is consistent with everything else, however “surprising” that might be;
  3. the proposed change does have its own downsides.

Unless presented with real major programming problems the current rules exhibit, I do not think the simple rule “scopes nest” needs a change that silently mutates program meaning.

Mike Miller: The rationale for the current specification is really very simple:

  1. “Unless redeclared in the derived class, members of a base class are also considered to be members of the derived class.” (11.7 [class.derived] paragraph 2)
  2. In class scope, members hide nonmembers.

That's it. Because template parameters are not members, they are hidden by member names (whether inherited or not). I don't find that “bizarre,” or even particularly surprising.

I believe these rules are straightforward and consistent, so I would be opposed to changing them. However, I am not unsympathetic toward Daveed's concern about name hijacking from base classes. How about a rule that would make a program ill-formed if a direct or inherited member hides a template parameter?

Unless this problem is a lot more prevalent than I've heard so far, I would not want to change the lookup rules; making this kind of collision a diagnosable error, however, would prevent hijacking without changing the lookup rules.

Erwin Unruh: I have a different approach that is consistent and changes the interpretation of the questionable code. At present lookup is done in this sequence:

If we change this order to

it is still consistent in that no lookup is placed between the base class and the derived class. However, it introduces another inconsistency: now scopes do not nest the same way as curly braces nest — but base classes are already inconsistent this way.

Nathan Myers: This looks entirely satisfactory. If even this seems like too big a change, it would suffice to say that finding a different name by this search order makes the program ill-formed. Of course, a compiler might issue only a portability warning in that case and use the name found Erwin's way, anyhow.

Gabriel Dos Reis: It is a simple fact, even without templates, that a writer of a derived class cannot protect himself against declaration changes in the base class.

Richard Corden: If a change is to be made, then making it ill-formed is better than just changing the lookup rules.

    struct B
    {
      typedef int T;
      virtual void bar (T const & );
    };

    template <typename T>
    struct D : public B
    {
      virtual void bar (T const & );
    };

    template class D<float>;

I think changing the semantics of the above code silently would result in very difficult-to-find problems.

Mike Miller: Another case that may need to be considered in deciding on Erwin's suggestion or the “ill-formed” alternative is the treatment of friend declarations described in 6.5.3 [basic.lookup.unqual] paragraph 10:

    struct A {
        typedef int T;
        void f(T);
    };
    template<typename T> struct B {
        friend void A::f(T);  // Currently T is A::T
    };

Notes from the October, 2005 meeting:

The CWG decided not to consider a change to the existing rules at this time without a paper exploring the issue in more detail.




1619. Definition of current instantiation

Section: 13.8.3.2  [temp.dep.type]     Status: open     Submitter: Johannes Schaub     Date: 2013-02-04

The definition of the current instantiation, given in 13.8.3.2 [temp.dep.type] paragraph 1, is phrased in terms of the meaning of a name (“A name refers to the current instantiation if it is...”); it does not define when a type is the current instantiation. Thus the interpretation of *this and of phrases like “member of a class that is the current instantiation” is not formally specified.




2250. Implicit instantiation, destruction, and TUs

Section: 13.8.4.1  [temp.point]     Status: open     Submitter: Dawn Perchik     Date: 2016-03-21

Consider:

  template<class T> struct A {
    T* p;
    A();
    ~A() {
	if (0 < sizeof (T))
	  delete p;
    }
  };
  struct U;
  struct C {
    C();
    ~C();
    A<U> u;
  };
  int main() {
    C c;
    return 0;
  }

The intent is that A<U>::~A is instantiated where the special member functions of C are defined (at which point U is ostensibly complete). Preventing instantiation (and thus avoiding the completeness check) in the present translation unit is necessary for the unique_ptr-based pimpl idiom to work.

Notes from the December, 2016 teleconference:

The problem is that the current wording only connects name lookup with point of instantiation; other semantic checks, such as the requirement for completeness of a class, should also be performed at that point.




1253. Generic non-template members

Section: 13.9  [temp.spec]     Status: open     Submitter: Nikolay Ivchenkov     Date: 2011-03-06

Many statements in the Standard apply only to templates, for example, 13.8 [temp.res] paragraph 8:

If no valid specialization can be generated for a template definition, and that template is not instantiated, the template definition is ill-formed, no diagnostic required.

This clearly should apply to non-template member functions of class templates, not just to templates per se. Terminology should be established to refer to these generic entities that are not actually templates.

Additional notes (August, 2012):

Among the generic entities that should be covered by such a term are default function arguments, as they can be instantiated independently. If issue 1330 is resolved as expected, exception-specifications should also be covered by the same term.

See also issues 1484 and 1626.

CWG 2022-11-11

With the introduction of the family of terms "templated entity", this has mostly been addressed. The remaining open issue is the timing details of the parsing of the complete-class contexts.




2435. Alias template specializations

Section: 13.9  [temp.spec]     Status: open     Submitter: Krystian Stasiowski     Date: 2019-09-28

According to 13.9 [temp.spec] paragraph 4,

An instantiated template specialization can be either implicitly instantiated (13.9.2 [temp.inst]) for a given argument list or be explicitly instantiated (13.9.3 [temp.explicit]). A specialization is a class, variable, function, or class member that is either instantiated or explicitly specialized (13.9.4 [temp.expl.spec]).

The definition of “specialization” does not cover alias templates, although the terms “specialization of an alias template” and ”alias template specialization” are used in 13.7.8 [temp.alias]. (Note that there are differences between alias specializations and the specializations mentioned here; in particular, an alias template cannot be explicitly specialized, and it is not the result of instantiation (paragraph 1) but simply of substitution (13.7.8 [temp.alias] paragraph 2).)




1378. When is an instantiation required?

Section: 13.9.2  [temp.inst]     Status: open     Submitter: Jason Merrill     Date: 2011-08-18

A template instantiation can be “required” without there being a need for it at link time if it can appear in a constant expression:

    template <class T> struct A {
       static const T t;
    };
    template <class T> const T A<T>::t = 0;
    template <int I> struct B { };
    int a = sizeof(B<A<int>::t>);

    template <class T> constexpr T f(T t) { return t; }
    int b = sizeof(B<f(42)>);

It seems like it might be useful to define a term other than odr-used for this sort of use, which is like odr-used but doesn't depend on potentially evaluated context or lvalue-rvalue conversions.

Nikolay Ivchenkov:

Another possibility would be to introduce the extension described in the closed issue 1272 and then change 6.3 [basic.def.odr] paragraph 2 as follows:

An expression E is potentially evaluated unless it is an unevaluated operand ( Clause 7 [expr]) or a subexpression thereof. if and only if

An expression S is a direct subexpression of an expression E if and only if S and E are different expressions, S is a subexpression of E, and there is no expression X such that X differs from both S and E, S is a subexpression of X, and X is a subexpression of E. A variable whose name appears as a potentially-evaluated expression is odr-used unless it is an object that satisfies the requirements for appearing in a constant expression (7.7 [expr.const]) and the lvalue-to-rvalue conversion (4.1) is immediately applied...

[Example:

    template <class T> struct X {
        static int const m = 1;
        static int const n;
    };
    template <class T> int const X<T>::n = 2;

    int main() {
        // X<void>::m is odr-used,
        // X<void>::m is defined implicitly
        std::cout << X<void>::m << std::endl;

        // X<void>::n is odr-used,
        // X<void>::n is defined explicitly
        std::cout << X<void>::n << std::endl;

        // OK (issue 712 is not relevant here)
        std::cout << (1 ? X<void>::m : X<void>::n) << std::endl;
    }

(See also issues 712 and 1254.)




1856. Indirect nested classes of class templates

Section: 13.9.2  [temp.inst]     Status: open     Submitter: Richard Smith     Date: 2014-02-11

During the discussion of issue 1484, it was observed that the current rules do not adequately address indirect nested classes of class templates (i.e., member classes of member classes of class templates) in regard to their potential separate instantiation.




2655. Instantiation of default arguments in lambda-expressions

Section: 13.9.2  [temp.inst]     Status: open     Submitter: Tom Honermann     Date: 2022-08-16

Subclause 7.5.5.2 [expr.prim.lambda.closure] paragraph 2 specifies:

The closure type is declared in the smallest block scope, class scope, or namespace scope that contains the corresponding lambda-expression.

This means the closure type is a local class if the lambda-expression appears at function scope. A note in 13.9.2 [temp.inst] paragraph 2 claims that default arguments inside local classes are not separately instantiated:

[Note 3: Within a template declaration, a local class (11.6 [class.local]) or enumeration and the members of a local class are never considered to be entities that can be separately instantiated (this includes their default arguments, noexcept-specifier s, and non-static data member initializers, if any, but not their type-constraints or requires-clauses). As a result, the dependent names are looked up, the semantic constraints are checked, and any templates used are instantiated as part of the instantiation of the entity within which the local class or enumeration is declared. —end note]

However, 13.9.2 [temp.inst] paragraph 3 is not in harmony with the note:

The implicit instantiation of a class template specialization does not cause the implicit instantiation of default arguments or noexcept-specifier s of the class member functions.

Example:

  template<typename T>
  void ft() {
   [](T p = T::value) {}; // error even though the lambda is never called
  }
  template void ft<int>();

Even for a lambda declared at namespace scope is an unused default argument instantiated by major implementations:

  template<typename T>
  using ta = decltype([](T p = T::value) { // error
	     return p;
	    });
  auto g = ta<int>{}(0);



293. Syntax of explicit instantiation/specialization too permissive

Section: 13.9.3  [temp.explicit]     Status: open     Submitter: Mark Mitchell     Date: 27 Jun 2001

13.9.3 [temp.explicit] defines an explicit instantiation as

Syntactically, that allows things like:

    template int S<int>::i = 5, S<int>::j = 7;

which isn't what anyone actually expects. As far as I can tell, nothing in the standard explicitly forbids this, as written. Syntactically, this also allows:

    template namespace N { void f(); }

although perhaps the surrounding context is enough to suggest that this is invalid.

Suggested resolution:

I think we should say:

[Steve Adamczyk: presumably, this should have template at the beginning.]

and then say that:

There are similar problems in 13.9.4 [temp.expl.spec]:

Here, I think we want:

with similar restrictions as above.

[Steve Adamczyk: This also needs to have template <> at the beginning, possibly repeated.]




1046. What is a “use” of a class specialization?

Section: 13.9.3  [temp.explicit]     Status: open     Submitter: Michael Wong     Date: 2010-03-08

According to 13.9.3 [temp.explicit] paragraph 10,

An entity that is the subject of an explicit instantiation declaration and that is also used in the translation unit shall be the subject of an explicit instantiation definition somewhere in the program; otherwise the program is ill-formed, no diagnostic required.

The term “used” is too vague and needs to be defined. In particular, “use” of a class template specialization as an incomplete type — to form a pointer, for instance — should not require the presence of an explicit instantiation definition elsewhere in the program.




264. Unusable template constructors and conversion functions

Section: 13.10.2  [temp.arg.explicit]     Status: open     Submitter: John Spicer     Date: 17 Nov 2000

The note in paragraph 5 of 13.10.2 [temp.arg.explicit] makes clear that explicit template arguments cannot be supplied in invocations of constructors and conversion functions because they are called without using a name. However, there is nothing in the current wording of the Standard that makes declaring a constructor or conversion operator that is unusable because of nondeduced parameters (i.e., that would need to be specified explicitly) ill-formed. It would be a service to the programmer to diagnose this useless construct as early as possible.




697. Deduction rules apply to more than functions

Section: 13.10.3  [temp.deduct]     Status: open     Submitter: Doug Gregor     Date: 6 June, 2008

13.10.3 [temp.deduct] is all about function types, but these rules also apply, e.g., when matching a class template partial specialization. We should add a note stating that we could be doing substitution into the template-id for a class template partial specialization.

Additional note (August 2008):

According to 13.7.6.2 [temp.spec.partial.match] paragraph 2, argument deduction is used to determine whether a given partial specialization matches a given argument list. However, there is nothing in 13.7.6.2 [temp.spec.partial.match] nor in 13.10.3 [temp.deduct] and its subsections that describes exactly how argument deduction is to be performed in this case. It would seem that more than just a note is required to clarify this processing.




1844. Defining “immediate context”

Section: 13.10.3  [temp.deduct]     Status: open     Submitter: Richard Smith     Date: 2014-01-28

The handling of an example like

  template<typename T, std::size_t S = sizeof(T)> struct X {};
  template<typename T> X<T> foo(T*);
  void foo(...);

  void test() { struct S *s; foo(s); }

varies among implementations, presumably because the meaning of “immediate context” in determining whether an error is a substitution failure or a hard error is not clearly defined.

Notes from the February, 2016 meeting:

See also issue 1554; the resolution of this issue should also deal with alias templates.




2296. Are default argument instantiation failures in the “immediate context”?

Section: 13.10.3  [temp.deduct]     Status: open     Submitter: Jason Merrill     Date: 2016-06-25     Liaison: EWG

Consider the following example (taken from issue 3 of paper P0348R0):

  template <typename U> void fun(U u = U());

  struct X {
    X(int) {}
  };

  template <class T> decltype(fun<T>()) g(int) { }

  template<> void g(long) { }

  int main() { g<X>(0); }

When is the substitution into the return type done? The current specification makes this example ill-formed because the failure to instantiate the default argument in the decltype operand is not in the immediate context of the substitution, although a plausible argument for making this a SFINAE case can be made.

Notes from the June, 2016 meeting:

CWG decided to refer this question to EWG for their consideration.

EWG 2022-11-11

This is tracked in github issue cplusplus/papers#1377.




2498. Partial specialization failure and the immediate context

Section: 13.10.3.1  [temp.deduct.general]     Status: open     Submitter: Daveed Vandevoorde     Date: 2021-06-15

Consider the following example:

  template<typename T, typename U> struct S {};
  template<typename T> struct S<T, T> {};
  template<typename T, typename U> struct S<T*, U*> {};
  template<typename... Ts> using V = void;
  template<typename T, typename U = void> struct X {};
  template<typename T> struct X<T, V<typename S<T, T>::type>>;
  X<int*> xpi;

Determining whether the partial specialization of X matches X<int*> requires determining whether one of the partial specializations of S matches S<int*,int*>. The partial specializations of S are ambiguous for this case. The question is whether that ambiguity should be considered in the “immediate context” of the type (SFINAE) or whether it should result in a hard error. There is implementation divergence on the handling of this example.

Notes from the November, 2021 teleconference:

A similar example can be constructed involving overload resolution instead of partial specialization:

  template<typename T, typename U> struct S {};
  template<typename T> struct S<T, T> {};
  template<typename T, typename U> struct S<T*, U*> {};

  template<class T>
  bool f(T, typename S<T, T>::type = 0);
  bool f(...);

  int x;
  bool b = f(&x);  // hard error with gcc, ok with clang



2672. Lambda body SFINAE is still required, contrary to intent and note

Section: 13.10.3.1  [temp.deduct.general]     Status: open     Submitter: Richard Smith     Date: 2022-09-30

Subclause 13.10.3.1 [temp.deduct.general] paragraph 9 specifies:

A lambda-expression appearing in a function type or a template parameter is not considered part of the immediate context for the purposes of template argument deduction. [Note 7: The intent is to avoid requiring implementations to deal with substitution failure involving arbitrary statements. ... -- end note ]

However, the intent of the note is not satisfied by the normative rule, because a lambda-expression appearing in a requires-expression has the same concerns as one in a function signature.

Suggested resolution: Change the rule to say that substitution into the body of a lambda is never in the immediate context of substitution into the lambda-expression and move the rule somewhere more general.




503. Cv-qualified function types in template argument deduction

Section: 13.10.3.2  [temp.deduct.call]     Status: open     Submitter: Gabriel Dos Reis     Date: 22 Feb 2005

Consider the following program:

    template <typename T> int ref (T&)                { return 0; }
    template <typename T> int ref (const T&)          { return 1; }
    template <typename T> int ref (const volatile T&) { return 2; }
    template <typename T> int ref (volatile T&)       { return 4; }

    template <typename T> int ptr (T*)                { return 0; }
    template <typename T> int ptr (const T*)          { return 8; }
    template <typename T> int ptr (const volatile T*) { return 16; }
    template <typename T> int ptr (volatile T*)       { return 32; }

    void foo() {}

    int main()
    {
        return ref(foo) + ptr(&foo);
    }

The Standard appears to specify that the value returned from main is 2. The reason for this result is that references and pointers are handled differently in template argument deduction.

For the reference case, 13.10.3.2 [temp.deduct.call] paragraph 3 says that “If P is a reference type, the type referred to by P is used for type deduction.” Because of issue 295, all four of the types for the ref function parameters are the same, with no cv-qualification; overload resolution does not find a best match among the parameters and thus the most-specialized function is selected.

For the pointer type, argument deduction does not get as far as forming a cv-qualified function type; instead, argument deduction fails in the cv-qualified cases because of the cv-qualification mismatch, and only the cv-unqualified version of ptr survives as a viable function.

I think the choice of ignoring cv-qualifiers in the reference case but not the pointer case is very troublesome. The reason is that when one considers function objects as function parameters, it introduces a semantic difference whether the function parameter is declared a reference or a pointer. In all other contexts, it does not matter: a function name decays to a pointer and the resulting semantics are the same.

(See also issue 1584.)




1939. Argument conversions to nondeduced parameter types revisited

Section: 13.10.3.2  [temp.deduct.call]     Status: open     Submitter: Richard Smith     Date: 2014-06-11

The intent of the resolution of issue 1184 appears not to have been completely realized. In particular, the phrase, “contains no template-parameters that participate in template argument deduction” in both the note in 13.10.3.2 [temp.deduct.call] paragraph 4 and the normative wording in 13.10.2 [temp.arg.explicit] paragraph 6 is potentially misleading and probably should say something like, “contains no template-parameters outside non-deduced contexts.” Also, the normative wording should be moved to 13.10.3.2 [temp.deduct.call] paragraph 4, since it applies when there are no explicitly-specified template arguments. For example,

  template<typename T>
  void f(T, typename identity<T>::type*);

Presumably the second parameter should allow pointer conversions, even though it does contain a template-parameter that participates in deduction (via the first function parameter).

Additional note, October, 2015:

See also issue 1391.




1221. Partial ordering and reference collapsing

Section: 13.10.3.5  [temp.deduct.partial]     Status: open     Submitter: Michael Wong     Date: 2010-11-08

The current partial ordering rules produce surprising results in the presence of reference collapsing.

Since partial ordering is currently based solely on the signature of the function templates, the lack of difference following substitution of the template type parameter in the following is not taken into account.

Especially unsettling is that the allegedly "more specialized" template (#2) is not a candidate in the first call where template argument deduction fails for it despite a lack of non-deduced contexts.

    template <typename T>
    void foo(T&&);  // #1

    template <typename T>
    void foo(volatile T&&);  // #2

    int main(void) {
      const int x = 0;
      foo(x);  // calls #1 with T='const int &'
      foo<const int &>(x);  // calls #2
    }



1763. Length mismatch in template type deduction

Section: 13.10.3.6  [temp.deduct.type]     Status: open     Submitter: Canada     Date: 2013-09-23

N3690 comment CA 4

It is not clear how an example like the following is to be handled:

  template <typename U>
  struct A {
    template <typename V> operator A<V>();
  };

  template <typename T>
  void foo(A<void (T)>);

  void foo();

  int main() {
    A<void (int, char)> a;
    foo<int>(a);
    foo(a); // deduces T to be int
  }

In sub13.10.3.6 [temp.deduct.type] paragraph 10, deduction from a function type considers P/A pairs from the parameter-type-list only where the "P" function type has a parameter. Deduction is not specified to fail if there are additional parameters in the corresponding "A" function type.

Notes from the September, 2013 meeting:

CWG agreed that this example should not be accepted. The existing rules seem to cover this case (deduction is not specified to “succeed,” so it's a reasonable conclusion that it fails), but it might be helpful to be clearer.




2417. Explicit instantiation and exception specifications

Section: 14.5  [except.spec]     Status: open     Submitter: John Spicer     Date: 2019-06-19

Consider the following example:

  template<class T>struct Y {
    typedef typename T::value_type blah;  // #1
    void swap(Y<T> &);
  };
  template<class T>
  void swap(Y<T>& Left, Y<T>& Right) noexcept(noexcept(Left.swap(Right))) { }

  template <class T> struct Z {
    void swap(Z<T> &);
  };
  template<class T>
  void swap(Z<T>& Left, Z<T>& Right) noexcept(noexcept(Left.swap(Right))) { }

  Z<int> x00, y00;
  constexpr bool b00 = noexcept(x00.swap(y00));
  template void swap<int>(Z<int>&, Z<int>&) noexcept(b00);  // #2

The question here is whether the explicit instantiation of

  swap<int>(Z<int>&, Z<int>&)

at #2 instantiates the exception specification of

  swap<int>(Y<int>&, Y<int>&)

which would instantiate Y<int>, resulting in an error on the declaration of

  typedef typename T::value_type blah;

at #1.

According to 13.9.2 [temp.inst] paragraph 14,

The noexcept-specifier of a function template specialization is not instantiated along with the function declaration; it is instantiated when needed (14.5 [except.spec]).

According to 14.5 [except.spec] bullet 13.3, one of the reasons an exception specification is needed is:

the exception specification is compared to that of another declaration (e.g., an explicit specialization or an overriding virtual function);

Such a comparison is presumably needed when determining which function template the explicit instantiation is referring to, making the program ill-formed. However, there is implementation variance on this point.

CWG 2022-11-10

There are related problems in this area; CWG is seeking input to form a holistic view.




2002. White space within preprocessing directives

Section: Clause 15  [cpp]     Status: open     Submitter: Richard Smith     Date: 2014-09-10     Liaison: WG14

According to Clause 15 [cpp] paragraphg 4,

The only white-space characters that shall appear between preprocessing tokens within a preprocessing directive (from just after the introducing # preprocessing token through just before the terminating new-line character) are space and horizontal-tab (including spaces that have replaced comments or possibly other white-space characters in translation phase 3).

The effect of this restriction is unclear, however, since translation phase 3 is permitted to transform all white space characters and comments into spaces. The relationship between these two rules should be clarified.




925. Type of character literals in preprocessor expressions

Section: 15.2  [cpp.cond]     Status: open     Submitter: Michael Wong     Date: 29 June, 2009

According to 15.2 [cpp.cond] paragraph 4,

The resulting tokens comprise the controlling constant expression which is evaluated according to the rules of 7.7 [expr.const] using arithmetic that has at least the ranges specified in 17.3 [support.limits], except that all signed and unsigned integer types act as if they have the same representation as, respectively, intmax_t or uintmax_t (_N3035_.18.4.2 [stdinth]). This includes interpreting character literals, which may involve converting escape sequences into execution character set members.

Ordinary character literals with a single c-char have the type char, which is neither a signed nor an unsigned integer type. Although 7.3.7 [conv.prom] paragraph 1 is clear that char values promote to int, regardless of whether the implementation treats char as having the values of signed char or unsigned char, 15.2 [cpp.cond] paragraph 4 isn't clear on whether character literals should be treated as signed or unsigned values. In C99, such literals have type int, so the question does not arise. If an implementation in which plain char has the values of unsigned char were to treat character literals as unsigned, an expression like '0'-'1' would thus have different values in C and C++, namely -1 in C and some large unsigned value in C++.




1436. Interaction of constant expression changes with preprocessor expressions

Section: 15.2  [cpp.cond]     Status: open     Submitter: Richard Smith     Date: 2012-01-02

It appears that some of the recent changes to the description of constant expressions have allowed constructs into preprocessor expressions that do not belong there. Some changes are required to restrict the current capabilities of constant expressions to what is intended to be allowed in preprocessor expressions.

Proposed resolution (February, 2012):

  1. Change 15.2 [cpp.cond] paragraph 2 as follows:

  2. Each preprocessing token that remains (in the list of preprocessing tokens that will become the controlling expression) after all macro replacements have occurred shall be in the lexical form of a token (5.6 [lex.token]). Any such token that is a literal (5.13.1 [lex.literal.kinds]) shall be an integer-literal, a character-literal, or a boolean-literal.
  3. Change 15.2 [cpp.cond] paragraph 4 as follows:

  4. ...using arithmetic that has at least the ranges specified in 17.3 [support.limits]. The only operators permitted in the controlling constant expression are ?:, ||, &&, |, ^, &, ==, !=, <, <=, >, >=, <<, >>, -, +, *, /, %, !, and ~. For the purposes of this token conversion...

Additional note (March, 2022):

The proposed resolution shown above allows lambda-expressions, which is undesirable.

CWG 2022-11-11

A possible approach, in addition to the proposed resolution above, is to prohibit the [ and { pp-tokens in the controlling expression of a conditional inclusion. C++ also permits using the alternative tokens, e.g. not, which might need clarification in the wording.




2190. Insufficient specification of __has_include

Section: 15.2  [cpp.cond]     Status: open     Submitter: Hubert Tong     Date: 2015-10-24

The following example ought to be well-formed, but the standard is insufficiently clear:

  #define X <ciso646> )

  #if __has_include( X && ( defined(X) )
  #endif

Also, there is insufficient prohibition for the formation of __has_include by token pasting when performing macro replacement for the controlling expression of a conditional inclusion.




2575. Undefined behavior when macro-replacing "defined" operator

Section: 15.2  [cpp.cond]     Status: open     Submitter: US     Date: 2019-10-23     Liaison: SG12

(From National Body comment US 130 on the C++20 DIS.)

Subclause 15.2 [cpp.cond] paragraph 10 specifies:

If the token defined is generated as a result of this replacement process or use of the defined unary operator does not match one of the two specified forms prior to macro replacement, the behavior is undefined.

Undefined behavior during lexing is not acceptable. The behavior ought to be well-defined, ill-formed, or conditionally-supported.

Additional notes (January, 2023):

Forwarded to SG12 with paper issue 1407, by decision of the CWG and SG12 chairs.




2576. Undefined behavior with macro-expanded #include directives

Section: 15.3  [cpp.include]     Status: open     Submitter: US     Date: 2019-10-23     Liaison: SG12

(From National Body comment US 131 on the C++20 DIS.)

Subclause 15.3 [cpp.include] paragraph 4 specifies:

If the directive resulting after all replacements does not match one of the two previous forms, the behavior is undefined.

Undefined behavior during lexing is not acceptable. This ought to be ill-formed.

Additional notes (January, 2023):

Forwarded to SG12 with paper issue 1408, by decision of the CWG and SG12 chairs.




2577. Undefined behavior for preprocessing directives in macro arguments

Section: 15.6.1  [cpp.replace.general]     Status: open     Submitter: US     Date: 2019-10-23     Liaison: SG12

(From National Body comment US 141 on the C++20 DIS.)

Subclause 15.6.1 [cpp.replace.general] paragraph 13 specifies:

If there are sequences of preprocessing tokens within the list of arguments that would otherwise act as preprocessing directives, [ Footnote: ...] the behavior is undefined.

Undefined behavior during lexing is not acceptable. The behavior ought to be ill-formed or conditionally-supported. Some implementation support an #if section, others reject that.

Additional notes (January, 2023):

Forwarded to SG12 with paper issue 1413, by decision of the CWG and SG12 chairs.




1625. Adding spaces between tokens in stringizing

Section: 15.6.3  [cpp.stringize]     Status: open     Submitter: Chandler Carruth     Date: 2013-02-18     Liaison: WG14

Given the following input,

  #define F(A, B, C) A ## x.B ## y.C ## z
  #define STRINGIFY(x) #x
  #define EXPAND_AND_STRINGIFY(x) STRINGIFY(x)
  char v[] = EXPAND_AND_STRINGIFY(F(a, b, c))

there is implementation variance in the value of v: some produce the string "ax.by.cz" and others produce the string "ax. by. cz". Although 15.6.3 [cpp.stringize] paragraph 2 is explicit in its treatment of leading and trailing white space, it is not clear whether there is latitude for inserting spaces between tokens, as some implementations do, since the description otherwise is written solely in terms of preprocessing tokens. There may be cases in which such spaces would be needed to preserve the original tokenization, but it is not clear whether the result of stringization needs to produce something that would lex to the same tokens.

See also issue 2522.

Notes from the April, 2013 meeting:

Because the preprocessor specification is primarily copied directly from the C Standard, this issue has been referred to the C liaison for consultation with WG14.




2578. Undefined behavior when creating an invalid string literal via stringizing

Section: 15.6.3  [cpp.stringize]     Status: open     Submitter: US     Date: 2019-10-23     Liaison: SG12

(From National Body comment US 142 on the C++20 DIS.)

Subclause 15.6.3 [cpp.stringize] paragraph 2 specifies:

If the replacement that results is not a valid character string literal, the behavior is undefined.

Undefined behavior during lexing is not acceptable. This ought to be ill-formed.

Additional notes (January, 2023):

Forwarded to SG12 with paper issue 1409, by decision of the CWG and SG12 chairs.




2522. Removing placemarker tokens and retention of whitespace

Section: 15.6.4  [cpp.concat]     Status: open     Submitter: Hubert Tong     Date: 2018-04-08     Liaison: WG14

It appears that the replacement of a function-like macro following further rescanning and replacement may begin or end with whitespace when the leftmost or rightmost tokens were placemarker tokens. GCC and MSVC preserve the whitespace in both the starting and ending positions. Clang and ICC preserve only the trailing whitespace. For example:

  #define F( X ) X##X/**/>
  #define G( X ) !/**/X##X
  #define F0( X ) F(X)
  #define G0( X ) G(X)
  #define STR2( X ) #X
  #define STR( X ) STR2(X)
  #define Q
  static_assert(STR(>F0(Q))[1] == '\u0020', STR(>F0(Q)));
    // ^ Works with GCC and MSVC; fails with Clang and ICC.
  static_assert(STR(G0(Q)=)[1] == '\u0020', STR(G0(Q)=));

This appears to be an issue in C as well; see ISO C 6.10.3.

See also issue 1625.




2579. Undefined behavior when token pasting does not create a preprocessing token

Section: 15.6.4  [cpp.concat]     Status: open     Submitter: US     Date: 2019-10-23     Liaison: SG12

(From National Body comment US 143 on the C++20 DIS.)

Subclause 15.6.4 [cpp.concat] paragraph 3 specifies:

If the result begins with a sequence matching the syntax of universal-character-name, the behavior is undefined. [Note: ... --end note] If the result is not a valid preprocessing token, the behavior is undefined.

Undefined behavior during lexing is not acceptable. The behavior ought to be well-defined, ill-formed, or conditionally-supported.

Additional notes (January, 2023):

Forwarded to SG12 with paper issue 1410, by decision of the CWG and SG12 chairs.




268. Macro name suppression in rescanned replacement text

Section: 15.6.5  [cpp.rescan]     Status: open     Submitter: Bjarne Stroustrup     Date: 18 Jan 2001     Liaison: WG14

It is not clear from the Standard what the result of the following example should be:

#define NIL(xxx) xxx
#define G_0(arg) NIL(G_1)(arg)
#define G_1(arg) NIL(arg)
G_0(42)

The relevant text from the Standard is found in 15.6.5 [cpp.rescan] paragraph 2:

If the name of the macro being replaced is found during this scan of the replacement list (not including the rest of the source file's preprocessing tokens), it is not replaced. Further, if any nested replacements encounter the name of the macro being replaced, it is not replaced. These nonreplaced macro name preprocessing tokens are no longer available for further replacement even if they are later (re)examined in contexts in which that macro name preprocessing token would otherwise have been replaced.

The sequence of expansion of G0(42) is as follows:

G0(42)
NIL(G_1)(42)
G_1(42)
NIL(42)

The question is whether the use of NIL in the last line of this sequence qualifies for non-replacement under the cited text. If it does, the result will be NIL(42). If it does not, the result will be simply 42.

The original intent of the J11 committee in this text was that the result should be 42, as demonstrated by the original pseudo-code description of the replacement algorithm provided by Dave Prosser, its author. The English description, however, omits some of the subtleties of the pseudo-code and thus arguably gives an incorrect answer for this case.

Suggested resolution (Mike Miller): Replace the cited paragraph with the following:

As long as the scan involves only preprocessing tokens from a given macro's replacement list, or tokens resulting from a replacement of those tokens, an occurrence of the macro's name will not result in further replacement, even if it is later (re)examined in contexts in which that macro name preprocessing token would otherwise have been replaced.

Once the scan reaches the preprocessing token following a macro's replacement list — including as part of the argument list for that or another macro — the macro's name is once again available for replacement. [Example:

    #define NIL(xxx) xxx
    #define G_0(arg) NIL(G_1)(arg)
    #define G_1(arg) NIL(arg)
    G_0(42)                         // result is 42, not NIL(42)

The reason that NIL(42) is replaced is that (42) comes from outside the replacement list of NIL(G_1), hence the occurrence of NIL within the replacement list for NIL(G_1) (via the replacement of G_1(42)) is not marked as nonreplaceable. —end example]

(Note: The resolution of this issue must be coordinated with J11/WG14.)

Notes (via Tom Plum) from April, 2004 WG14 Meeting:

Back in the 1980's it was understood by several WG14 people that there were tiny differences between the "non-replacement" verbiage and the attempts to produce pseudo-code. The committee's decision was that no realistic programs "in the wild" would venture into this area, and trying to reduce the uncertainties is not worth the risk of changing conformance status of implementations or programs.

Additional notes (June, 2022):

It is unclear whether the phrasing "following the macro's replacement list" in the suggested resolution covers this case:

  #define f(x) b
  #define b(x) x(1)

  f(1)(f) // the result is 1



2580. Undefined behavior with #line

Section: 15.7  [cpp.line]     Status: open     Submitter: US     Date: 2019-10-23     Liaison: SG12

(From National Body comments US 144 and US 145 on the C++20 DIS.)

Subclause 15.7 [cpp.line] paragraph 3 specifies:

If the digit sequence specifies zero or a number greater than 2147483647, the behavior is undefined.

Subclause 15.7 [cpp.line] paragraph 5 specifies:

If the directive resulting after all replacements does not match one of the two previous forms, the behavior is undefined; otherwise, the result is processed as appropriate.

Undefined behavior during lexing is not acceptable. This ought to be ill-formed.

Additional notes (January, 2023):

Forwarded to SG12 with paper issue 1411, by decision of the CWG and SG12 chairs.




2693. Escape sequences for the string-literal of #line

Section: 15.7  [cpp.line]     Status: open     Submitter: CWG     Date: 2023-02-07     Liaison: WG14

The grammar for #line is inconsistent between 15.7 [cpp.line] paragraph 4:

# line digit-sequence " s-char-sequenceopt " new-line

and 15.7 [cpp.line] paragraph 1:

The string-literal of a #line directive, if present, shall be a character string literal.

The characters comprising the string-literal are not necessarily interpreted for e.g. escape sequences or universal-character-names, so the use of string-literal here is questionable. This directive names a source file, thus it might be better to use header-name (5.8 [lex.header]) instead.




2581. Undefined behavior for predefined macros

Section: 15.11  [cpp.predefined]     Status: open     Submitter: US     Date: 2019-10-23     Liaison: SG12

(From National Body comments US 148 and US 149 on the C++20 DIS.)

Subclause 15.11 [cpp.predefined] paragraph 4 specifies:

If any of the pre-defined macro names in this subclause, or the identifier defined, is the subject of a #define or a #undef preprocessing directive, the behavior is undefined.

Undefined behavior during lexing is not acceptable. This ought to be ill-formed.

Additional notes (January, 2023):

Forwarded to SG12 with paper issue 1412, by decision of the CWG and SG12 chairs.




897. _Pragma and extended string-literals

Section: 15.12  [cpp.pragma.op]     Status: open     Submitter: Daniel Krügler     Date: 9 May, 2009

The specification of how the string-literal in a _Pragma operator is handled does not deal with the new kinds of string literals. 15.12 [cpp.pragma.op] says,

The string literal is destringized by deleting the L prefix, if present, deleting the leading and trailing double-quotes, replacing each escape sequence...

The various other prefixes should either be handled or prohibited.

Additional note (October, 2013):

If raw string literals are supported, the question of how to handle line splicing is relevant. The wording says that “the characters are processed through translation phase 3,” which is a bit ambiguous as to whether that includes phases 1 and 2 or not. It would be better to be explicit and say that the processing of phase 3 or of phases 1 through 3 is applied.




2694. string-literals of the _Pragma operator

Section: 15.12  [cpp.pragma.op]     Status: open     Submitter: CWG     Date: 2023-02-07     Liaison: WG14

Subclause 15.12 [cpp.pragma.op] paragraph 1 specifies:

A unary operator expression of the form:
      _Pragma ( string-literal )
is processed as follows: The string-literal is destringized by deleting the L prefix, if present, deleting the leading and trailing double-quotes, replacing each escape sequence \" by a double-quote, and replacing each escape sequence \\ by a single backslash. The resulting sequence of characters is processed through translation phase 3 to produce preprocessing tokens that are executed as if they were the pp-tokens in a pragma directive. The original four preprocessing tokens in the unary operator expression are removed.

In contrast, C23 section 6.10.6 specifies:

A unary operator expression of the form:
    _Pragma ( string-literal )
is processed as follows: The string literal is destringized by deleting any encoding prefix, ...

While in C, any encoding prefix is deleted, C++ only deletes the L prefix and does not consider UTF-8, UTF-16, and UTF-32 string literals. This was probably an oversight when C++ obtained UTF-x string literals.

However, a string-literal entails lexing of escape sequences and universal-character-names, which seems not useful given that the string literal is de-stringized immediately afterwards. It might be more appropriate to employ a simpler lexical structure such as a q-char-sequence as used in a header-name instead. See also issue 2693.




2361. Unclear description of longjmp undefined behavior

Section: 17.13.3  [csetjmp.syn]     Status: open     Submitter: Zhihao Yuan     Date: 2017-10-20

According to 17.13.3 [csetjmp.syn] paragraph 2,

A setjmp/longjmp call pair has undefined behavior if replacing the setjmp and longjmp by catch and throw would invoke any non-trivial destructors for any automatic objects.

The intent is clear, that transferring control from point A to point B via longjmp has undefined behavior if throwing an exception at point A and catching it at point B would invoke non-trivial destructors. The wording could be more precise.

See also the corresponding editorial issue for additional discussion.

Notes from the October, 2018 teleconference:

There are a number of unanswered questions in the current wording, including the impact on the current exception (whether it still exists) after a longjmp out of a handler, the impact on the initialization of locks if jumping from the initialization of a local static data member, etc. One thought was to restrict use of longjmp to “plain C functions”. Another was to say if the program would have different behavior via the use of exceptions the behavior is undefined. There was no consensus on how to proceed.

Additional note, January, 2022:

See also library issues 1265 and 3652.




1944. New C incompatibilities

Section: Clause Annex C  [diff]     Status: open     Submitter: Mike Miller     Date: 2014-06-18

Some new features of C++ not only introduce incompatibilities with previous versions of C++ but also with C; however, the organization of Annex Clause Annex C [diff] makes it difficult to specify that a given feature is incompatible with both languages, and the practice has been only to document the C++ incompatibilities. Some means of specifying both sets of incompatibilities should be found, hopefully without excessive duplication between the C and C++ sections.




1248. Updating Annex C to C99

Section: C.6  [diff.iso]     Status: open     Submitter: Jonathan Wakely     Date: 2011-02-28

The description of incompatibilities with C in Annex C.6 [diff.iso] refers to C89, but there are a number of new features in C99 that should be covered.