November 15, 2016·4 min read·engineeringcppstandardsexceptions

Issaquah removes dynamic exception lists from the C++17 draft

C++17 ballot work removes nonempty dynamic exception specifications. The change simplifies exception contracts while preserving throw() as a deprecated spelling with noexcept semantics.

An exception specification that lists permitted exception types can look like a useful interface contract while imposing a runtime mechanism with awkward failure semantics. C++ has already deprecated that facility. At Issaquah, the committee agreed to remove nonempty dynamic exception specifications from the C++17 draft. Herb Sutter's November 15 meeting report places the removal within the work of resolving the standard's national-body ballot comments.

The technical history is in Alisdair Meredith's P0003R5, dated November 11. Its introduction proposes removing the dynamic facility while retaining empty throw() as a deprecated alias for noexcept(true). That qualification matters. A description that says every old exception-specification spelling disappears would misstate the compatibility decision.

I regard RAII as the strongest argument for C++, and exception behavior is part of understanding it. Resource release through destructors depends on the control flow the language actually specifies. Removing a discouraged exception mechanism reduces one of the historical distinctions a reader must carry when examining older interfaces.

The interface becomes a throwing distinction

Meredith describes the old specification model in terms of sets of permitted exceptions. noexcept instead expresses whether a function is non-throwing or potentially throwing. The proposed removal lets the standard describe exception behavior using that distinction without preserving a separate set-based mechanism outside the new model.

An old declaration and two possible modern declarations make the choices visible. They are alternative interface policies, not equivalent replacements for every program.

struct operation_error {};

void legacy_operation() throw(operation_error); // old dynamic list
void operation();                              // potentially throwing
void release_resource() noexcept;              // non-throwing

The first declaration uses a nonempty list removed from the C++17 draft. Removing that list produces a potentially throwing declaration. Writing noexcept instead makes a stronger commitment, so it is not a mechanical way to preserve a declaration that previously permitted operation_error to escape.

P0003R5 explains why the distinction matters operationally. An exception outside the old list invokes the unexpected-exception machinery. Removing the list removes that behavior, and can allow an exception to propagate where the old mechanism would translate it or terminate. A source edit that restores compilation can therefore still change failure handling.

The paper discusses a more explicit workaround for programs that need to preserve rejection of other exception types. A function-try-block can rethrow an allowed exception and terminate for other exceptions. That is more intrusive than deleting a suffix because the program is now expressing a runtime policy in its body. The need for that policy depends on what callers actually relied upon.

Meredith traces the mechanism back to the original exception design and notes that the standard library largely avoided dynamic lists. C++11 deprecated them while introducing noexcept for the important non-throwing case. That history explains why the removal targets a facility already discouraged, rather than asking users to abandon a recently recommended interface style. It also explains the remaining migration work. Deprecation permits a feature to remain in conforming source, so removing it still affects code that continued to use it during the intervening releases.

Empty throw specifications remain with changed semantics

throw() is a special compatibility case. Meredith argues that it was much more widely used than nonempty lists, often to mark functions intended not to throw. Retaining the spelling avoids breaking that body of source, while assigning it noexcept(true) semantics removes dependence on the old unexpected-exception mechanism.

The change is therefore more than a grammatical exemption. Previously, the empty dynamic specification had its own unwinding and handler behavior. The new interpretation follows the non-throwing model. Code that used the spelling remains readable to the compiler, but a reader still needs to know which language mode establishes its semantics.

P0003R5 explicitly keeps this spelling deprecated. Preserving old source is different from recommending the spelling for new interfaces. A header intended for modern C++ can state the non-throwing property directly with noexcept, while compatibility work may still encounter the retained form.

The paper also connects removal with the effort to make exception specifications part of function types. Leaving the obsolete dynamic mechanism alongside that work would retain another special case that implementations and programmers must understand. Simplifying the supported model is one motivation for removing the nonempty lists during this revision.

Ballot resolution is still part of producing C++17

Sutter reports that the draft reached its major feature freeze before Issaquah and then underwent its comment ballot. The meeting's main task was addressing those review comments. The committee expected two meetings for that work, and the report describes Issaquah as the first. C++17 is therefore still in the process of technical review, rather than a published standard that every toolchain already implements.

The report gives another example of that review in std::variant. It says the type was clarified so that its alternatives cannot be an empty pack, void or reference types. Those constraints shape what a library interface may represent. They also show why ballot work can matter to application code even when a meeting adds no broad new facility.

Technical specifications proceed separately. Sutter reports completion of Library Fundamentals 2 and comment ballots for Networking and Ranges. These documents provide experience with library designs outside the main standard. Their progress at the same meeting does not make their facilities part of C++17.

This separation is useful when interpreting compiler support claims. A compiler can implement an extension or a technical specification without the facility becoming a requirement of the core standard. Conversely, a draft removal may require compatibility work before switching an established project to that language mode. The meeting report records standards progress, while a toolchain's own documentation establishes its implementation status.

I favor removing the nonempty exception lists because their cost is concentrated in a part of the language where mistaken assumptions affect cleanup and failure handling. The compatibility exception for throw() also explains why older code cannot be judged by spelling alone. For the C++17 draft after Issaquah, the relevant interface decision is whether an operation is potentially throwing or non-throwing, with any policy that rejects particular exception types expressed separately in executable code.