9
\$\begingroup\$

When you turn up the compiler warnings with -Wunused-variable, one tends to then employ an UNUSED() macro, to say something is intentionally not used at this time.

But that's not very binding, since it's generally just a void cast (or near-equivalent). Hence if you get the warning, add an UNUSED(), and some time later actually do use the variable...it's misleading to to the reader of the code.

I got the odd idea of making C++11 give this some level of "enforcement", by seeing if it got in a POD lvalue it could trash it. Obviously won't work for everything (unused constants, etc.) But I kind of fiddled with it until it had the effect I wanted--while I can't say exactly why every variation I tried didn't work.

You could break it out in various ways to do something special for std::is_pointer<> or other types, or even have some kind of "trashme()" method detection for non-POD values. But here's the core of the idea:

#if !defined(NDEBUG)

template<
    typename T,
    typename TRR = typename std::remove_reference<T>::type,
    typename std::enable_if<
        !std::is_lvalue_reference<T &&>::value
        || std::is_const<TRR>::value
        || !std::is_pod<TRR>::value
    >::type* = nullptr
>
void UNUSED(T && v) {
    static_cast<void>(v);
}

template<
    typename T,
    typename TRR = typename std::remove_reference<T>::type,
    typename std::enable_if<
        std::is_lvalue_reference<T &&>::value
        && !std::is_const<TRR>::value
        && std::is_pod<TRR>::value
    >::type* = nullptr
>
void UNUSED(T && v) {
    memset(&v, 0xBD, sizeof(TRR)); // or 0xDECAFBAD pattern, etc.
}

#endif

Anything cleaner/simpler to achieve the same effect, or ideas to make it better?

\$\endgroup\$
2
  • 2
    \$\begingroup\$ Are you searching for this attribute? \$\endgroup\$ Mar 31, 2017 at 12:31
  • \$\begingroup\$ @Incomputable Interesting to know that exists, and that someone was specific enough about the semantics to add "maybe" onto it. (If you can't solve the problem, point out that you can't...!) But the trick here is I'm actually using C++11 to instrument a codebase that is committed to using only C, so the conventional UNUSED() is what it has to be working with. \$\endgroup\$ Mar 31, 2017 at 12:49

1 Answer 1

4
+50
\$\begingroup\$

Missing headers

This code needs

#include <type_traits>
#include <cstring>

Use sizeof operator on a value

While we can write sizeof (type), in this case we have a value, and we can use it here - sizeof will read through the reference, saving us some work:

std::memset(&v, 0xBD, sizeof v);

Reduce repetition

There are two expressions that need to be maintained in parallel in the std::enable_if template. We can refactor these into a single trait template:

template<typename T>
struct is_mutable_pod_lvalue
    : std::integral_constant<bool,
                             std::is_lvalue_reference<T&&>::value
                             && !std::is_const<typename std::remove_reference<T>::type>::value
                             && std::is_pod<typename std::remove_reference<T>::type>::value>
{};

Then the functions reduce to

template<typename T>
typename std::enable_if<!is_mutable_pod_lvalue<T>::value>::type
UNUSED(T && v) {
    static_cast<void>(v);
}

template<typename T>
typename std::enable_if<is_mutable_pod_lvalue<T>::value>::type
UNUSED(T && v) {
    std::memset(&v, 0xBD, sizeof v);
}

In C++14, we could use the _t and _v forms to avoid typing ::type and ::value respectively, and in C++17, we could use std::bool_constant instead of std::integral_constant.

If we were using C++17, we could further reduce to a single function, by moving the test within the function body:

template<typename T>
UNUSED(T&& v) {
    if constexpr (is_mutable_pod_lvalue<T>::value)
        std::memset(&v, 0xBD, sizeof v);
}

Now we're no longer using is_mutable_pod_value more than once, we can just inline it back again:

template<typename T>
void UNUSED(T&& v) {
    static const bool is_mutable_pod_lvalue =
        std::is_lvalue_reference<T&&>::value
        && !std::is_const<typename std::remove_reference<T>::type>::value
        && std::is_pod<typename std::remove_reference<T>::type>::value;

    if constexpr ((is_mutable_pod_lvalue)
        std::memset(&v, 0xBD, sizeof v);
}

Enhancement: accept multiple arguments

Here's a varargs template that makes it easier to use:

template<typename T, typename... U>
void UNUSED(T&& v, U&&... u)
{
    UNUSED(v);
    UNUSED(u...);
}

Again, C++17 makes this simpler, with fold expressions:

template<typename... T>
void UNUSED(T&&... v)
{
    (UNUSED(v) , ...);
}

A definition is required when NDEBUG is set

Probably just an oversight, but the code is missing an #else block. It's probably worth reordering so that the positive branch comes first (confusing because NDEBUG is itself a negative...). Further, since the only difference between the debug and non-debug cases it the use of std::memset() in one of the implementations, we can reduce the scope of #ifdef to just that (at a small cost to non-debug compilation times):

template<typename T>
typename std::enable_if<is_mutable_pod_lvalue<T>::value>::type
UNUSED(T&& v)
{
    static_cast<void>(v);
#ifndef NDEBUG
    std::memset(&v, 0xBD, sizeof v);
#endif
}

Revised code

#include <type_traits>
#include <cstring>
template<typename T>
struct is_mutable_pod_lvalue
    : std::integral_constant<bool,
                             std::is_lvalue_reference<T&&>::value
                             && !std::is_const<typename std::remove_reference<T>::type>::value
                             && std::is_pod<typename std::remove_reference<T>::type>::value>
{};

template<typename T>
typename std::enable_if<!is_mutable_pod_lvalue<T>::value>::type
UNUSED(T&& v)
{
    static_cast<void>(v);
}

template<typename T>
typename std::enable_if<is_mutable_pod_lvalue<T>::value>::type
UNUSED(T&& v)
{
    static_cast<void>(v);
#ifndef NDEBUG
    std::memset(&v, 0xBD, sizeof v);
#endif
}

template<typename T, typename... U>
void UNUSED(T&& v, U&&... u)
{
    UNUSED(v);
    UNUSED(u...);
}
#include <memory>
void f(int i, const int ci, int& ri, const int& cri, int *pi,
       std::unique_ptr<int> ui, std::unique_ptr<int>&& rrui)
{
#ifdef TEST_UNUSED
    UNUSED(i, ci, ri, cri, pi, ui, rrui);
#endif
}

Four compilations are required to test this fully for all combinations of TEST_UNUSED and NDEBUG.

\$\endgroup\$
6
  • \$\begingroup\$ May be you could bring the curly braces down? The absence of curly braces make me feel like the definition is not done yet. Though that may be the problem with the post width, on my screen it would probably be much higher. \$\endgroup\$ May 5, 2017 at 9:24
  • \$\begingroup\$ It took me a while to understand what you mean (I thought by "bring down" that you meant "reduce the number of"). I see now that you just wanted the template's definition {} on a new line, which makes sense - now done. \$\endgroup\$ May 5, 2017 at 10:03
  • \$\begingroup\$ Possible enhancement - for a non-POD mutable value, could we placement-delete, and then overwrite the storage? We'd need a way to reinstate it to a valid state before it goes out of scope and meets its destructor... \$\endgroup\$ May 5, 2017 at 10:19
  • \$\begingroup\$ Perhaps UNUSED<T> could be a non-copyable class containing a T and a T&, which would then move-construct the value into its own storage when constructed, and move it back in the destructor? \$\endgroup\$ May 5, 2017 at 10:35
  • \$\begingroup\$ that sounds interesting. Though will increase constraints on type. Is runtime cost of any value in the macro? \$\endgroup\$ May 5, 2017 at 10:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.