std::ignore – Elegant solution to a simple problem

If you love tuple (you probably do already), there’s a clever way to have clean code, similar to python, to pass parameters for which you don’t actually care to have the value to. To restate that, an easy way to extract only 1 or 2 of the values from a multi-value tuple and ignoring the rest. A common example is with inserting a value into a set. If you want to verify if a value is already in the set, a common idiom is to insert the value in the set, and verify the return value to determine if the value was in fact inserted. If the value was inserted, then the value wasn’t already in the set. Mystery solved. Note: I use a pair in this example instead of tuple. Same idea.

First, let’s look at a lengthier implementation without using std::ignore:

std::set<std::string> mySet;

// insert some values in the set...

// return type is a pair of < iterator, bool >
using SetRetType = 
   pair<std::set<std::string>::iterator, bool>;
	
SetRetType retVal = mySet.insert("Value");
if (retVal.second)
{
	// New value inserted, do something...
}

Using the templated function std::tie, which takes the passed arguments and constructs a tuple from it, we can produce cleaner and easier to read code. Notice we also got rid of the “using” alias SetRetType.

std::set<std::string> mySet;

// insert some values in the set...

// Only one variable is declared. 
bool newValueInserted = false;
std::tie(std::ignore, newValueInserted)  = 
   mySet.insert("Value");

if (newValueInserted)
{
	// New value inserted, do something...
}

It’s worth mentioning that set::insert returns a pair, and std::tie returns a tuple. However tuples work well with pairs. You can construct a tuple from a pair, which is what we’re doing in this line:

std::tie(std::ignore, newValueInserted)  = 
   mySet.insert("Value");

So far this results in some much cleaner code. As long as your fellow code reviewer knows about std::tie, it’s quite easy to follow what this code intends to do. Which is often the case for elegant code. Leaving the fun of code reviews behind, let’s focus on the interesting part of HOW this code actually does it. Particularly how std::ignore, well, is ignored. So far, we know it’s some kind of instance of a type since we can pass it as a argument. Let’s see a possible implementation:

// STRUCT Ignore
struct _Ignore
{	
   // struct that ignores assignments
   template<class Type>
   void operator=(const Type&) const
   {	
      // do nothing	
   }
};

constexpr _Ignore ignore{};

The first interesting point to notice is the struct _Ignore has a templated assignment operator. This allows it to handle the type it is assigned to. The second point to notice is the const-ness. The global variable instance is a constexpr itself. But also is the assignment operator which is const, allowing it to be used with consts. (As a side note I’ve not often seen const assignment operator as they “generally” would defeat the assignment purpose). Finally since the resulting code is likely to be optimized away or at least result in no code being generated. The compiler will however likely take a hit as the template code will need to instantiated for each type it is used for, however most likely imperceptible. Still, it’s an elegant and concise implementation, which I quite like.

Other uses:

Another use for std::ignore which I’m not the fondest of, but is sometimes used, is to trick the compiler into thinking a variable is in fact used when it is not. This is something you see if you use GCC, as declaring a variable and not using it will result in and error. And well, if you treat warnings as errors, you’ll need to fix the warning. Most often, it’s a variable you declared and forgot to remove. But some are in fact used, just maybe not in this compilation. For example:

bool valueIsValidWithSideEffect( int myVal );

… 
	
bool isValid = valueIsValidWithSideEffect(myVal);
#ifdef TESTING
if (!isValid)
   // DO something...
#endif

In this case if TESTING isn’t defined, we don’t use variable isValid, and we get the following warning in GCC:

warning: unused variable ‘retVal’ [-Wunused-variable]

A possible solution to get around this warning is to assign the variable to std::ignore, thereby using it, as such:

bool isValid = valueIsValidWithSideEffect(myVal);
#ifdef TESTING
if (!isValid)
   // output something...
#elsif
   std::ignore = isValid;

This gets around the compiler warning. In this simple example, it doesn’t seem necessary as we can do away with the assignment. It does seem to me like the tail (compiler) wagging the dog (code). We usually in this case have the option of assigning to void. In the case of an expression (possibly in a try catch block for validation)

// option 1: 
(void)(validateVals1() + validateVals2());

// option 2:
std::ignore = validateVals1() + validateVals2();

It’s possible the use of std::ignore makes it easier to understand. However I’d argue slowing down the compiler and the extra code bloat from needing to include <tuple> where std::ignore is defined as a tradeoff for extra readability in this case seems like overkill. But still, it’s a “trick” worth knowing.

Leave a Reply

Your email address will not be published. Required fields are marked *