Maybe Bindings Are Unused

Since my last post, trunk GCC has implemented experimental support for all current C++17 draft features, as per the release notes. I almost immediately went home to build myself a fresh copy, I shouldn’t have bothered since Matt Godbolt’s compiler explorer provides a snapshot build.

Structured Bindings

One of the C++17 features I and my colleagues are most excited about is structured bindings or decomposition declarations. There have been plenty of great posts explaining this feature so I won’t go into much detail, here’s a contrived example wrapping std::frexp and returning a float, int pair.

std::pair<float, int> frexp(float arg) {
  int exponent;
  float mantissa = std::frexp(arg, &exponent);
  return {mantissa, exponent};
}

auto [mantissa, exponent] = frexp(0.23f);
std::cout << mantissa << std::endl;

This syntax reminds me a lot of Python which pleases me.

mantissa, exponent = math.frexp(0.23)
print mantissa

Often when using library functions which return multiple things you may only care about one of them. Unfortunately when used in conjunction with a tool like pylint which aggressively emits warnings when variables are unused this can be a pain point. For such a case the special _ variable name can be used to signify it is unused.

mantissa, _ = mathc.frexp(0.23)
print mantissa

Maybe it can be unused?

In my last post I explored the use of the [[maybe_unused]] attribute, also new to the C++17 standard. Its primary purpose is to silence compiler warnings for variables which may or may not be used, sounds pretty similar to the special _ variable name in Python.

Here is the first thing I tried, with the compiler flags -std=c++17 -Wall. My thinking here was to mirror as closely as possible the [[maybe_unused]] int var; syntax whilst only applying the attribute to exponent.

auto [mantissa, [[maybe_unused]] exponent] = frexp(0.23f);
std::cout << mantissa << std::endl;

Sadly this results in parsing errors which mess up the decomposition.

error: expected identifier before '[' token
error: expected ']' before '[' token
error: only 1 names provided while 'std::pair<float, int>' decomposes into 2 elements

Next up I swapped the order of the attribute and the variable name.

auto [mantissa, exponent [[maybe_unused]]] = frexp(0.23f);
std::cout << mantissa << std::endl;

This results in another parse error in addition to the unused variable warning.

error: expected ']' before '[' token
warning: unused variable 'exponent' [-Wunused-variable]

Lastly I tried using the attribute on the whole expression, matching the defined syntax for a single variable.

[[maybe_unused]] auto [mantissa, exponent] = frexp(0.23f);
std::cout << mantissa << std::endl;

This compiles but sadly does not silence the warning.

warning: unused variable 'exponent' [-Wunused-variable]

Is this an omission from the specification or a compiler bug?

Digging Deeper

The original proposal for structured bindings does address the question of explicitly ignoring variables. The first approach suggested using std::ignore_t to mirror std::ignore used with std::tie. It also mentions using wild cards such as _ or * but these were precluded on the grounds of not having support for pattern matching in the language. Notably there is no mention of using the [[maybe_unused]] attribute in the proposal, likely because they were developed in parallel.

Looking at the C++ draft, the spec language for Decomposition declaration references attribute-specifier-seq as part its definition.

attribute-specifier-seq ~opt~ decl-specifier-seq ref-qualifier ~opt~ e initializer ;

This matches up with the behaviour seen in my last example as the expression is accepted by the compiler. Elsewhere the language for Maybe unused attribute states that.

The attribute may be applied to the declaration of a class, a typedef-name, a variable, a non-static data member, a function, an enumeration, or an enumerator.

I think this is the crux of the issue as decomposition declarations are not mentioned in the spec language for the maybe unused attribute. However a different interpretation, where a decomposition declaration is interpreted as defining a variable (or a few), would imply that unused variable warnings should be silenced.

A Small Change

A possible addition to explicitly add support for decomposition declarations to the maybe unused attribute could be along of the lines of the following.

The attribute may be applied to the declaration of a class, a typedef-name, a variable, any variable declared in a decomposition declaration, a non-static data member, a function, an enumeration, or an enumerator.

Which would apply to the following decomposition declaration and encourage implementations not to emit an unused variable warning for exponent in this case.

[[maybe_unused]] auto [mantissa, exponent] = frexp(0.23f);
std::cout << mantissa << std::endl;

Of course I’m no expert when it comes to the C++ specification so I am probably missing some other interaction or design constraint.

Conclusion

There seems to be a missing piece to the puzzle and I believe its a small piece. Decomposition declarations already have a way to specify attributes on them. A small addition to the language defining what maybe unused attributes apply to, explicitly allowing decomposition declarations and the variables they define, could enable use of structured bindings in more places in more codebases with stricter compiler warnings enabled.