A lesser known C++ operator

Dealing with function template code can sometimes result in errors being emitted by the compiler because template argument deduction is ambiguous. Most of the time, casts are used to battle these errors, but often there’s a more elegant solution to the problem.

The following code shows a very simple function template for returning the maximum of any two values, build as a template in order for it to work on any type:

template <typename T>
T Max(T lhs, T rhs)
{
  return (lhs > rhs) ? lhs : rhs;
}

Such a template function can either be called with or without an explicit template argument:

// explicit template argument
int max = Max<int>(100, 200);

// template argument omitted
int max = Max(100, 200);

If the template argument when calling a template function is omitted, figuring out the correct type T is up to the compiler, which is done using a process known as template argument deduction.

But when dealing with template argument deduction, the values involved do not undergo standard conversions such as type promotions:

int i = 100;
int max = Max(i, 200u);

The above code will result in the following error:

error C2782: 'T Max(T,T)' : template parameter 'T' is ambiguous
could be 'unsigned int'
or       'int'

Of course, in this example we could have just written 200 instead of 200u and that would have fixed the problem, but it gets more interesting when dealing with enums and class conversion operators:

enum Test
{
  VALUE_1 = 1,
  VALUE_10 = 10
};

struct TestClass
{
  operator int(void)
  {
    return 10;
  }
};

// ambiguous
int max = Max(100, VALUE_10);

// explicit cast to tell the compiler the desired type
int max = Max(100, static_cast<int>(VALUE_10));

// ambiguous
TestClass tc;
int max = Max(100, tc);

// again, explicit cast
TestClass tc;
int max = Max(100, static_cast<int>(tc));

As can be seen, even though 100 and VALUE_10 can easily be treated as ints, the template argument deduction process is ambiguous, because the types the compiler sees are int and Test, hence it doesn’t know which one to choose. The same happens with instances of type TestClass, which defines a conversion operator to int – in both cases we need to add an explicit cast as a workaround.

It would be nice if we could somehow tell the C++ compiler to carry out conversion and type promotion first, before deducing template arguments.

Luckily, it turns out that there is a way to do that. Observe:

// works with type promotion
int max = Max(100, +VALUE_10);

// works with conversion operators
TestClass tc;
int max = Max(100, +tc);

Notice the plus operator?

This operator is known as the unary plus operator, one of the lesser known C++ operators. It can also be used for forcing other implicit conversions in C++, such as array decay.

Nice, another trick to add to our bag of C++ “nice-to-knows”.

About these ads

One thought on “A lesser known C++ operator

  1. Sadly, this approach doesn’t work with long longs, unsigned long longs, floats, doubles, or long doubles. Unary plus on any of these types yields a value with the same type, i.e. no promotion took place. If Max() is called on an int (or shorter) and a long long (or unsigned long long or float or double or long double), the unary plus trick isn’t enough to tell the compiler that we want the int to be promoted to match the type of the wider argument.

    Perhaps what’s needed is a function-like operator that takes one or more types or expressions as arguments (similar to how sizeof can take a type or expression as an argument) and “returns” a type (similar to how a typedef’ed symbol “evaluates” to a type) which would be the result of cross-promoting types across the arguments. To avoid making yet another keyword, let’s just call it typename.

    template
    typename(T1, T2) Max(T1 lhs, T2 rhs)
    {return lhs > rhs ? lhs : rhs;}

    Note that the comma between typename’s parentheses is not the comma operator, it’s a separator. Otherwise the type of the expression would just be T2. Want the comma operator, for whatever reason? Use another pair of parentheses.

    C++ insists on requiring the function’s return type to be named before the function is implemented. If C++ didn’t do this, maybe it could figure it out from the actual return. Turns out that this is actually possible, by using macros instead of functions! To get it right and avoid the repeated side effect problem (Max(i++, j++)), GCC typeof and statement expressions are required. Here’s the example from http://gcc.gnu.org/onlinedocs/gcc-4.6.2/gcc/Typeof.html :

    #define Max(a, b) ({\
    typeof (a) _a = (a);\
    typeof (b) _b = (b);\
    _a > _b ? _a : _b;})

    With my above proposal, typename could take the place of typeof.

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