Boost logo

Boost :

From: Howard Hinnant (hinnant_at_[hidden])
Date: 2002-04-29 15:58:07


On Monday, April 29, 2002, at 03:39 PM, Mat Marcus wrote:

> I won't be able to check email over the next couple of weeks as
> I'll be on vacation, but I wanted to share a little technique
> that we (Dietmar Kuehl, Jaako Jarvi, Jermey Siek, and Mat
> Marcus) came up with in Curacao. It turns out that in some
> limited circumstances it is possible to check whether an
> expression is valid at compile time. The example case below is
> an is_addable metafunction.

Very cool guys!

> Or we could write
> template functions which won't be considered during overload
> resolution if the necessary operators are not supported by the
> template parameters (using default function argument tricks).

I'm not sure this has been presented yet. So in the interest of
clarification, here is how to turn a compile-time test into a restricted
template. Probably best explained with a motivating example:

As you know, integral arguments in math functions work in C90, C99, but
not C++98:

#include <math.h>

int main()
{
        double x = sqrt(2); // legal C, illegal C++, ambiguous
(sqrt(float), sqrt(double),...)
}

We want to add a templated sqrt so that clients can send integral
arguments to sqrt:

template <class NumType>
inline
double
sqrt(NumType x)
{
        return sqrt((double)x);
}

However, this can break existing user code that overloads sqrt on a
user-defined type, and calls it via an implicit conversion:

#include <cmath>

namespace std
{

template <class NumType>
inline
double
sqrt(NumType x)
{
        return sqrt((double)x);
}

} // std

using namespace std;

struct SmallNum {};

struct BigNum
{
        BigNum() {}
        BigNum(SmallNum) {}
};

BigNum sqrt(BigNum) {return BigNum();}

int main()
{
        SmallNum s;
        BigNum b = sqrt(s); // error, calls std::sqrt<SmallNum>
}

This code would've worked had we not added the templated sqrt.

So what we would like is to restrict the template so that it will only
accept integral arguments and otherwise tell the compiler to look for
other viable overloads.

The following incantation will do the trick:

namespace std
{

using boost::is_integral;

template <bool> struct restrict_to {};
template <> struct restrict_to<true> {typedef int type;};

template <class NumType>
inline
double
sqrt(NumType x, typename
restrict_to<is_integral<NumType>::value>::type* = 0)
{
        return sqrt((double)x);
}

} // std

Now when the template sqrt is inspected with a type that is not
integral, the complier won't be able to form the type for the defaulted
second argument because of 14.8.2/2, thus template argument deduction
fails and the template function will not be added to the list of viable
functions.

In general, this is a technique for transforming any compile time traits
query into a restricted template. The technique works both at namespace
scope, and at class scope for member templates. Thus the "do the right
thing" clause (23.1.1/9-11) can also be implemented this way.

This technique also generalizes to multiple constraints that may
interrelate multiple template arguments.

All that is needed is the trivial restrict_to struct, and an appropriate
compile time test returning a const bool.

(tested on CodeWarrior Pro 8 beta)

-Howard


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk