May 2, 2017·6 min read·engineeringcppcompilerstemplates

GCC 7 implements the C++17 draft with compile-time branching

GCC 7 offers experimental support for the current C++17 draft. if constexpr changes template instantiation, while structured bindings and deduction reduce repetition at interfaces.

An ordinary if with a constant condition does not give a template permission to contain type-dependent operations that are invalid for the chosen argument. Optimization can remove an unneeded branch after the program has met the language's requirements, but it cannot make that earlier requirement disappear. if constexpr changes instantiation itself. GCC 7 implements it as part of experimental support for the complete current C++17 draft.

The GCC 7 release page dates 7.1 to May 2. The C++ changes specify -std=c++1z and -std=gnu++1z for the experimental draft modes. They list compile-time branching alongside structured bindings, class template argument deduction and non-type template parameters using auto. That is a substantial implementation milestone, with experimental status still part of the project's claim.

I find the instantiation change more useful than its small syntax suggests. Templates can provide efficient abstraction while making simple alternatives expensive to express and diagnose. A branch whose unused dependent code does not need instantiation removes some of the need to distribute one logical operation across several helper templates.

The discarded statement has a defined status

Jens Maurer's P0292R2 provides the wording for if constexpr. The condition must be a contextually converted constant expression of type bool. Depending on that value, one substatement is designated as discarded. During the relevant template instantiation, a discarded substatement does not instantiate when the condition is no longer value-dependent.

That rule applies before the optimizer decides which machine instructions are useful. The distinction matters for templates that select different operations for different types. Each selected specialization can instantiate the code appropriate to its argument without requiring the other dependent operation to be valid for that argument too.

#include <type_traits>

template<class T>
auto read_value(T input)
{
    if constexpr (std::is_pointer_v<T>) {
        return *input;
    } else {
        return input;
    }
}

For a suitable pointer argument, the chosen branch dereferences the pointer. For an ordinary integer argument, the other branch returns that integer. The integer specialization does not need to instantiate the dependent dereference. This example concerns type selection and assumes that any pointer actually dereferenced points to a valid object.

It is also an example of return-type deduction. The wording says discarded return statements do not participate in deducing a function's placeholder return type. Each specialization therefore deduces its result from the non-discarded branch. An ordinary runtime conditional has no corresponding permission to disregard one return type merely because optimization can establish a constant condition.

Compile-time selection does not erase all language checks

The facility is not conditional preprocessing. Both branches remain C++ source, and the special rule concerns instantiation of a discarded substatement. The paper explicitly says it does not propose disabling every static_assert in a branch that is not taken. It gives an unconditional false assertion as an invalid example.

That qualification is important when writing error branches in generic code. A programmer cannot assume that surrounding arbitrary invalid source with if constexpr(false) makes it acceptable. The template rules still distinguish dependent constructs from defects that make every specialization invalid. The new branch form changes a specific instantiation obligation, not the entire translation process.

Maurer's wording also restricts jumps across the statement boundaries. Labels within a substatement can be referred to only from that same substatement, and switch labels have corresponding association rules. The selected code must still have coherent control flow after the other statement is discarded.

The paper gives a recursive parameter-pack example where the branch recurses only when arguments remain. The final specialization does not instantiate a recursive call with an empty argument list. This is a concrete use of the rule that can replace an otherwise separate base-case helper, while preserving the compiler's ability to specialize the code for the actual pack.

Structured bindings name parts of a result

GCC's draft support also includes structured bindings. Thomas Köppe's April change catalogue describes them for arrays, suitable public-member classes and types following the tuple-like protocol. Their practical use is to give meaningful local names to the components returned by an operation.

A map insertion is a natural example because it yields both a position and a Boolean result. The decomposition can name those parts at the operation, so later statements do not need to repeatedly express them through pair member names. This changes local expression of the result without creating a different insertion policy.

#include <map>

bool insert_if_missing(std::map<int, int>& values, int key, int value)
{
    auto [position, inserted] = values.try_emplace(key, value);
    return inserted && position->second == value;
}

The example uses the names to describe the insertion outcome. It does not infer whether the mapped value existed from equality alone, since an existing element might already hold the supplied value. Keeping the Boolean result explicit preserves that distinction in the interface returned by the library.

Structured bindings also require attention to the declaration's form. Decomposing a result is not a general promise to copy every component or to retain a reference to every source. The declaration and the applicable decomposition rules establish that relationship. Their benefit is a direct spelling of the components, with the language still responsible for their types and lifetimes.

Deduction reduces repeated template arguments

Class template argument deduction is another implemented draft feature. The change catalogue illustrates a pair whose template arguments come from constructor arguments. This allows the type's use to omit information the compiler can infer from initialization, and library authors can supply deduction guides for additional control.

A factory can keep class deduction at the construction expression and use auto for its function return type. This avoids treating a class template name without arguments as an unrestricted replacement for an ordinary type name.

#include <utility>

auto make_entry()
{
    return std::pair{7, 11};
}

The distinction prevents a useful shorthand from becoming an inaccurate claim that a class template name can omit arguments in every type position. Constructor-based deduction and placeholder return-type deduction solve related repetition problems at different language locations.

Library support has its own scope

The GCC 7 notes separately describe experimental C++17 support in libstdc++. They list string_view and value wrappers including optional and variant, alongside invocation utilities and other additions. Language acceptance and library availability both matter to the examples a program can compile.

The notes also provide controls for particular language changes outside draft mode. Over-aligned allocation can be enabled with -faligned-new, and expression-order requirements have a -fstrong-eval-order control. Those options are specific implementation choices. They do not make enabling one isolated flag equivalent to selecting all C++17 semantics.

One library change in the release has a compatibility consequence unrelated to new syntax. The exception type used by iostreams, std::ios_base::failure, now uses the C++11 ABI according to the release notes. That is a reminder that selecting a language facility and linking a library implementation are separate parts of the toolchain. A program can encounter a library compatibility issue without adding a structured binding or a compile-time branch.

The new invocation vocabulary helps generic code in another way. The notes list std::invoke, std::is_invocable and std::invoke_result. Köppe's catalogue describes invocation as a uniform way to apply callable entities using the same model as the standard library's invocation rules. That gives an algorithm author a library operation for a task previously described partly through specification machinery. It is a distinct improvement from using a constraint to select an overload or a branch.

The library also adds logical traits with short-circuiting behavior in the metaprogramming sense. Köppe explains that specializations not required to determine the result are not instantiated. That resembles the goal of if constexpr in avoiding unnecessary instantiation, but the facilities operate at different levels. The traits compose compile-time predicates, while the statement selects which code inside a specialization needs instantiation. Choosing the appropriate facility can keep a simple condition from becoming an elaborate set of helper definitions.

The change catalogue also distinguishes std::reduce from std::accumulate. The newer algorithm does not guarantee one particular order of operations, which matters when the operation's result depends on grouping or order. A new algorithm name is therefore not automatically a drop-in spelling for an older call. This is another example of why complete draft support should be read as a set of precise facilities. Some simplify expression of existing behavior, while others deliberately offer a different contract that an application must choose.

I prefer treating GCC 7's draft support as an opportunity to simplify specific generic interfaces while keeping those boundaries explicit. if constexpr removes unnecessary dependent instantiation, structured bindings expose components of a result, and deduction can remove repeated constructor type arguments. Each has a defined role that can reduce helper code. The release's experimental status and documented language flags remain part of any accurate statement about the toolchain implementing that code.