Boost logo

Boost :

From: Doug Gregor (dgregor_at_[hidden])
Date: 2007-10-18 16:50:40


On Oct 16, 2007, at 5:58 AM, Marco Costalba wrote:

> Starting to hack with boost libraries I found myself more and more
> stumbling across a big brick of C++ language, i.e. SFINAE works on
> types only, not on expressions.
>
> If an expression is ill formed there's no SFINAE around that will
> avoid you a barfing compiler.

This issue has been actively discussed in the C++ committee. You can
track it's progress via core issue 339 on the Core Issues list, which
is here:

        http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#339

At the very bottom of the issue text, there is a note that describes
the latest view on the matter. The resolution we're likely to get is
that SFINAE will be extended to support expressions. There are still
cases that will cause "hard" failures (not SFINAE failures), for
example if one ends up trying to instantiate a class template inside
a sizeof() expression and that instantiation fails.

> Looking at C++0x I failed to see a future improvement in that
> direction, also 'concepts' that will be quite new, apart from helping
> in decoding error messages (and this is a BIG help I would add), does
> not immediately seem to extend the language in that direction.

Concepts are actually quite a big improvement in this area. Mathias
Gaunard mentioned how the use of concepts along with (concept-based)
overloading can solve the kind of problem you're describing, where
you need to query the properties of a type and decide between two
alternatives. Concepts allow you to describe the properties of types
via a set of requirements. For example, here's a short concept that
describes some properties of numeric types:

        concept Numeric<typename T> {
                T::T(T const &);

                T operator+(T, T);
                T operator-(T, T);
                T operator*(T, T);
                T operator/(T, T);
                
                T operator-(T);
                T operator+(T);
        }

Many types meet the requirements of this concept: int, float, double,
and a user-defined bigint, for example, would all have the necessary
copy constructor and arithmetic operations.

Using concepts, we can write generic algorithms as "constrained"
templates, e.g.,

        template<typename T>
        requires Numeric<T>
        T twice(T x)
        {
                return x + x;
        }

Now, for integral types (int, long, etc.), we would really rather
implement twice() with a simple shift operation. So, we first define
a new concept that captures the notion of an integral type:

        concept Integral<typename T> : Numeric< T> {
                T operator<<(T, int);
                T operator>>(T, int);
        }

Integral is a *refinement* of Numeric, meaning that it inherits all
of the requirements of the Numeric concept. So, to be an integral
type on needs to have all of the operations from Numeric (+, -, *, /,
etc.) and from Integral (<<, >>). Every type that is Integral is also
Numeric.

Using the Integral concept, we can write an overload of the twice()
function:

        template<typename T>
        requires Integral<T>
        T twice(T x)
        {
                return x << 1;
        }

Now, when a user calls twice() with a particular type, the compiler
will find both overloads. If the type is Numeric, the first overload
works and will be chosen; if the type is Integral, both overloads
work but the second (more specific, more efficient) overload will be
chosen. So instead of some kind of "try_compile" block, you just
write down the requirements you need to check (e.g., whether the type
is integral, whether it has a << operator, etc.) as a concept, and
then write two overloads: one if that property is satisfied, one if
that property is not satisfied.

        - Doug


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