Defective Language

Overloading the broken universal reference ‘T&&’

C++11 introduced a perfect forwarding mechanism for template parameters. The key part is the ability for a template parameter to match any input without any implicit conversion. This is done using the ‘&&’ operator and has become known as the universal reference. While it does work, it suffers from one serious flaw: it can’t be specialized/overloaded. In this article I present a solution to that problem.

Basic Form

The universal reference is typically used in a template function which forwards its parameters to another function. A very basic function looks like this:

template<typename T>
void apply( T&& value ) {
	std::forward<T>( value );
}

A real function would do something more than just forward the value, but here we’re interested just in the universal reference mechanism. The ‘T&&’ part is the key: it says this function should match any input parameter. It is complemented with ‘std::forward’ which retains the exact type when calling the other function. There are several detailed explanations of this part online (refer Scott Meyers), so I’d rather move on to the problem bit here.

The Problem

The problem arises when trying to overload a universal reference for a specific type. Here I mean an unqualified type: one without any lvalue, rvalue, or other modifiers. For example, we wish to write this overload:

void apply( my_type&& value ) {
	...
}

We want this function to match ‘my_type’, ‘my_type &’, ‘my_type &&’ and any const modified forms. What we’ve unfortunately written is a function which only matches an rvalue reference. This gets really confusing, so I’ve written a short example to show the problem.

#include <iostream>

template<typename T>
void apply( T&& value ) {
	std::cout << "apply" << std::endl;
}

struct my_type { };
void apply( my_type && value ) {
	std::cout << "my_type" << std::endl;
}

int main() {
	apply( my_type() );
	my_type a;
	apply( a );
	apply( static_cast<my_type const>(a) );
	apply( static_cast<my_type const &>(a) );
}

Run example

The overloaded ‘my_type’ version is only called one time: the first function call in ‘main’. The three other calls are all to the generic version. The problem is a consistency defect in the C++ standard. While a template ‘T&&’ is a universal reference that matches anything, ‘my_type&&’ is strictly an rvalue reference.

There is no proper way to write the overload we intend. Attempts to specialize the template instead will get the same result: only one function call will match. Continue with the overload method and we’ll be forced to write multiple versions of the function.

void apply(my_type const & value ) {
	std::cout << "my_type const&" << std::endl;
}
void apply(my_type & value ) {
	std::cout << "my_type const&" << std::endl;
}

If you make this change you’ll see that one of the calls is still to the generic version. By trying to add one which covers the last call I end up getting ambiguity problems. Switching to templates I can get a different set called, but still not a full fix. We could play all day with this nonsense and never get the intended solution.

An Avoidance Solution

Universal references and forwarding are great in some cases, but they simply aren’t needed everywhere. If you don’t have a specific need to capture rvalue references you might just forgo this approach all together. For example, if our ‘apply’ function above needs only to read from the value we can simply use a const reference instead of the universal one. This is the pre-C++11 approach and it still works in many cases.

#include <iostream>

template<typename T>
void apply(T const & value ) {
	std::cout << "apply" << std::endl;
}

struct my_type { };
void apply(my_type const & value ) {
	std::cout << "my_type" << std::endl;
}

int main() {
	apply( my_type() );
	my_type a;
	apply( a );
	apply( static_cast<my_type const>(a) );
	apply( static_cast<my_type const &>(a) );
}

Run example

This version of the code now calls our overloaded ‘my_type’ version in all cases.

A Universal Solution

A const-reference of course does you no good if you actually need the universal reference. To do this, without duplicating several versions of your overload, you need to resort to a bit of template trickery. I at first attempted to get this working with variations on ‘enable_if’. I was only able to get a single overload working; as I added more I got redefinition errors. So I resorted to a different trickery.

The only way to actually get a universal reference parameter is to use a template. This means all of our overloads must be template functions. The same underlying reason also means we can’t specialize the templates: we need to use actual overloads. And there is no way to avoid redefinition errors with just one parameter, thus we need at least one extra parameter to overload. It would of course be very inconvenient if the caller had to know anything about this.

The solution involves introducing a tag parameter. First, look at the generic ‘apply’ function using this approach.

template<typename T>
struct class_tag { };

template<typename TF>
void apply( TF && f ) {
	//get the unqualified type for the purpose of tagging
	typename class_tag<typename std::decay<TF>::type> tag;
	apply_impl( std::forward<TF>(f), tag );
}

template<typename TF, typename Tag>
void apply_impl( TF && f, Tag ) {
 	std::cout << f << std::endl;
}

I’ve split the function into two parts now: the generic ‘apply’ call and the ‘apply_impl’ which actually does the processing. This separation exists so that the ‘apply’ function can be called without any knowledge of the underlying tag system.

The ‘class_tag’ is used as an overload helper. Our generic ‘apply_impl’ will match any tag parameter, serving as the fallback for all non-specialized types. The trick is how ‘apply’ adds this extra parameter to the function call. It makes use of the ‘std::decay’ feature: this gives us an unqualified form of the template type. For example, ‘std::decay<match_a const &>’ results in ‘match_a’.

The unqualified type is then used to instantiate a ‘class_tag’ object. This means that every call to ‘apply_impl’ will be done with a tag type which is unique for each unqualified type. Unique types mean we can use them to overload a function without ambiguity. Thus if we wish to overload a ‘match_a’ type we do this:

template<typename TF>
void apply_impl( TF && f, class_tag<match_a> ) {
	std::cout << "match_a" << std::endl;
}

This function now applies to the family of ‘match_a’ types: lvalue, rvalue, const, value, etc. We’ve successfully overloaded a universal reference. Here is a full code example to show it all working together.

#include <iostream>
#include <type_traits>

template<typename T>
struct class_tag { };

template<typename TF>
void apply( TF && f ) {
	//get the unqualified type for the purpose of tagging
	class_tag<typename std::decay<TF>::type> tag;
	apply_impl( std::forward<TF>(f), tag );
}

template<typename TF, typename Tag>
void apply_impl( TF && f, Tag ) {
 	std::cout << f << std::endl;
 }

struct match_a { };
template<typename TF>
void apply_impl( TF && f, class_tag<match_a> ) {
	std::cout << "match_a" << std::endl;
}

struct match_b { };
template<typename TF>
void apply_impl( TF && f, class_tag<match_b> ) {
	std::cout << "match_b" << std::endl;
}

template<typename TF>
void apply_impl( TF && f, class_tag<int*> ) {
	std::cout << "int*" << std::endl;
}

int main() {
	apply( 12 );
	apply( "hello" );
	apply( match_a() );
	apply( match_b() );

	match_a a;
	apply(a);
	apply( static_cast<match_a const&>(a) );
	apply( static_cast<match_a const>(a) );

	int b[5];
	apply(b);
	apply(static_cast<int*>(b));
}

Run example

Commentary

I’ve seen some discussions about whether the “universal reference” actually exists. Technically ‘T&&’ is rvalue reference notation that when combined with type deduction rules gives it the universal behaviour. There is no ‘universal reference’ in the standard, though it clearly intended for this functionality. The problem is that ‘T&&’ in a template and ‘my_type&&’ in a normal function behave quite differently, and they serve very different purposes in typical code.

While the universal reference and perfect forwarding is a much welcomed feature in C++, the implementation presented is rather underwhelming. The ‘&&’ notation should not have been given two different meanings. Trying to explain away the issue with type deduction rules is just nonsense. What matters is the result, and the result is that I have to resort to trickery to get a basic overload working. I shouldn’t have to do that, thus I consider it a defect in the C++ language.

Categories: Defective Language, Programming

Tagged as: ,

6 replies »

  1. While it is perhaps confusing that `T&&` matches lvalue references, `my_class&&` is very different from `T&&`. `T&&` is much more generic, since T can be any type — including an lvalue reference. In the case of `my_class&&` you are explicitly disallowing that possibility.

    • Yes, that’s exactly the problem. There is no natural way to override a universal reference for a specific unqualified type.

  2. Nice discussion. I has wondering what is the best way to extend this to variadic template arguments. An example: something make_shared-like where you want to make a specialization for certain types of arguments.

    • At the terminal function is where you put the overloads. That is, in a variadic expansion you always end up calling a particular function for a single argument. It is at this level where you would put the overloads. That was actually what I was working on when I decided to write this article. ;)

  3. I took the following approach for an arbitrary number of elements:

    #include 
    #include 
    
    void print()
    {
    }
    template 
    void print(Arg &amp;&amp; arg, Args &amp;&amp; ... args)
    {
    	std::cout &lt;&lt; arg &lt;&lt; std::endl;
    	print(std::forward(args)...);
    }
    
    template 
    struct Tag 
    {
    };
    
    struct CBufferIBoolean
    {
    	CBufferIBoolean(bool) {}
    	CBufferIBoolean(bool, int) {}
    };
    
    template 
    at_Class* gf_xMakeRefImpl(Tag ac_ClassTag,
    	Tag&lt;typename std::decay::type &gt; &amp;&amp; ... ac_ArgumentTags,
    	at_Arguments &amp;&amp; ... ac_Arguments)
    {
    	std::cout &lt;&lt; &quot;General implementation&quot; &lt;&lt; std::endl;
    	print(ac_Arguments...);
    	return new at_Class(std::forward(ac_Arguments)...);
    }
    
    template 
    CBufferIBoolean* gf_xMakeRefImpl(Tag ac_ClassTag, Tag ac_ArgumentTag, at_Argument &amp;&amp; ac_Argument)
    {
    	std::cout &lt;&lt; &quot;overload implementation&quot; &lt;&lt; std::endl;
    	print(ac_Argument);
    	return new CBufferIBoolean(std::forward(ac_Argument)); // TODO: return static object instances, don't new
    }
    
    template 
    at_Class* gf_xMakeRef(at_Arguments &amp;&amp; ... ac_arguments)
    {
    	return gf_xMakeRefImpl&lt;typename std::decay::type, at_Arguments...&gt;(Tag &lt; typename std::decay::type &gt;(),
    		Tag &lt; typename std::decay::type &gt; ()...,
    		std::forward(ac_arguments) ...);
    }
    
    int main()
    {
    	gf_xMakeRef(true);
    	gf_xMakeRef(true, 5);
    	return 0;
    }
    
  4. Now it compiles on all tested compilers and it lets me to change object state in rvalue reference implementation, but it doesn’t looks very nice, and this is because the same syntax of universal references and rvalue references.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s