Mandatory template arguments

Consider the following function declaration:

template <typename ... Items_To_Print>
void print_as_csv(std::ostream & stream, char delimiter, Items_To_Print && ... items_to_print);

Though the declaration may seem rather involved, the actual application of this function is quite simple:

print_as_csv(std::cout, ';', 2.0, 42, "hello");

The compiler knows how to create an instance of print_as_csv by a mechanism called template argument deduction. It works only for functions, because the compiler needs arguments to deduce the types from. There are, however, template functions for which not all template arguments can be deduced by the compiler.

When template argument deduction fails

Think of make_unique, a sibling of std::make_shared which we have presented in an earlier article. Its signature is repeated here:

template <typename Value, typename ... Arguments>
std::unique_ptr<Value> make_unique(Arguments && ... arguments_for_constructor);

The template parameter Value of make_unique cannot be determined by the arguments passed to it, so the user has to specify it herself. It is difficult to infer this information on a quick glance of the function’s signature. For each template argument we have to check whether it appears in the list of function arguents. Worse still, if one forgets to specify such template parameters, some compilers provide error messages of little help. For the line

auto message = make_unique("hallo");

g++ generates the following error message:

error: no matching function for call to ‘make_unique(const char [6])’
error: unable to deduce ‘auto’ from ‘<expression error>’

Although the message is technically correct, I know from own and repeated experience that a message in the line of “Dude, you forgot a template argument” might save some time. In general, we would like an easy way to highlight template arguments the compiler cannot deduce automatically and help the compiler to print a helpful error
message.

Working around the compiler

To this end, we create a new header file, perhaps called mandatory.h to hold the following code:

#include <type_traits>

namespace implementation {

	struct unspecified_type;

	template <typename Random_Type>
	class mandatory
	{
		struct private_type;

		static_assert(std::is_same<Random_Type, private_type>::value, 
			"You forgot to specify a mandatory template argument which cannot be deduced."
		);
	};
} 

typedef implementation::mandatory<implementation::unspecified_type> mandatory;

The static_assert  does the actual work of creating a legible error message. However, we need to make sure that we delay the evaluation of the condition to the last possible moment. If the condition in static_assert would be just plain false or equivalent (such as std::is_same<double, int>::value), the compiler would terminate the compilation
immediately. For usability we provide the typedef mandatory, which we use to modify the signature of make_unique:

#include "mandatory.h"

template <typename Value = mandatory, typename ... Arguments>
std::unique_ptr<Value> make_unique(Arguments && ... arguments_for_constructor)

Now it is clear from the signature that you need to specify Value yourself. If you forget to (just as in the example shown above), you get the following error message:

error: static assertion failed: "You forgot to specify a mandatory template argument which cannot be deduced."
make_unique.h: In function ‘std::unique_ptr<Value> make_unique(Arguments&& ...)
    [with Value = implementation::mandatory<implementation::unspecified_type>,
          Arguments = {const char (&)[6]}]’:

Of course, if you specify the mandatory template argument as intended, our feature stays perfectly invisible.

All in all, mandatory is a small helper class designed to work around bad compiler messages and make template function signatures an easier read.

Leave a Reply

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