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.