HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

UNUSED() with teeth in C++11 debug builds

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
buildswithdebugunusedteeth

Problem

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::type,
    typename std::enable_if::value
        || std::is_const::value
        || !std::is_pod::value
    >::type* = nullptr
>
void UNUSED(T && v) {
    static_cast(v);
}

template::type,
    typename std::enable_if::value
        && !std::is_const::value
        && std::is_pod::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?

Solution

Missing headers

This code needs

#include 
#include 


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
struct is_mutable_pod_lvalue
    : std::integral_constant::value
                             && !std::is_const::type>::value
                             && std::is_pod::type>::value>
{};


Then the functions reduce to

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

template
typename std::enable_if::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
UNUSED(T&& v) {
    if constexpr (is_mutable_pod_lvalue::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
void UNUSED(T&& v) {
    static const bool is_mutable_pod_lvalue =
        std::is_lvalue_reference::value
        && !std::is_const::type>::value
        && std::is_pod::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
void UNUSED(T&& v, U&&... u)
{
    UNUSED(v);
    UNUSED(u...);
}


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

template
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 std::enable_if::value>::type
UNUSED(T&& v)
{
    static_cast(v);
#ifndef NDEBUG
    std::memset(&v, 0xBD, sizeof v);
#endif
}


Revised code

#include 
#include 
template
struct is_mutable_pod_lvalue
    : std::integral_constant::value
                             && !std::is_const::type>::value
                             && std::is_pod::type>::value>
{};

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

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

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


#include 
void f(int i, const int ci, int& ri, const int& cri, int *pi,
       std::unique_ptr ui, std::unique_ptr&& 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.

Code Snippets

#include <type_traits>
#include <cstring>
std::memset(&v, 0xBD, sizeof v);
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) {
    std::memset(&v, 0xBD, sizeof v);
}
template<typename T>
UNUSED(T&& v) {
    if constexpr (is_mutable_pod_lvalue<T>::value)
        std::memset(&v, 0xBD, sizeof v);
}

Context

StackExchange Code Review Q#159439, answer score: 4

Revisions (0)

No revisions yet.