Boost logo

Boost :

From: Paul Moore (gustav_at_[hidden])
Date: 1999-12-20 18:09:56

OK, I think I have this clarified. I got myself a copy of the standard and
worked through things.

I'll address the issue of defining boost::rational<I> and
boost::abs(rational<I>) in such a way that they "do what we want".
Implementation of non-member functions relating to a class are basically the
only "hard" issue in the implementation of rational<>.

Koenig lookup

The ideal user interface for the rational class would be

    using boost::rational;
    rational<int> r = ...;
    r = abs(r);

This usage is sanctioned by Koenig lookup (std: 3.4.2).

For compilers which don't implement Koenig lookup, it is necessary to add a
"using boost::abs", to make the abs(rational) declaration visible. There is no
workaround here - this need is precisely what Koenig lookup was developed to

On the implementation side, abs(rational<I>) needs to be implemented in terms of
(unqualified) abs(I), so that the unqualified lookup rules can come into play to
find an appropriate abs() for I. To cater for I being int or long, a suitably
placed "using std::abs" is needed.

Friend templates

The correct syntax for a friend template declaration is

    friend rational<IntType> abs<> (const rational<IntType>);

(std 14.5.3). I say this with some hesitation, as MSVC generated compiler errors
with some variations on this, and gcc wasn't too robust - and my reading of the
standard is slightly confused here.

In this instance, rational<> exposes numerator() and denominator() member
functions, and so abs() doesn't need to be a friend, in practice.

I would categorise friend templates as not yet well enough supported to use.

MSVC bugs

We know MSVC doesn't implement Koenig lookup, and doesn't like friend templates.
OK, call these bugs or "not yet supported new language features". Regardless, we
know how to deal with them.

The big problems are

    - MSVC doesn't put the contents of <cstdlib> in std::
    - This is compounded by broken name lookup rules

The lack of std::abs should in theory just mean that we don't need a using
std::abs() in the implementation of abs(rational<I>). Unfortunately, it looks
like MSVC compounds this by failing to follow std, by not counting ::abs
as a candidate for overloading along with boost::abs. Or maybe it isn't doing
the overloading rules correctly - wasn't there an older overloading rule which
preferred "closer" declarations over "further away" ones? Regardless, MSVC isn't
doing what the standard says.

Actually, on a close reading of, "A name used in the definition of a
function..." I note that no explicit mention is made of the global scope
(although you could argue that the global scope encloses all namespace scopes -
but I couldn't find words to that effect - and the example does imply that it is
intended to be included). I'm sure that either I missed something, or this is
worth a defect report - it is clearly intended.

Anyway, as a consequence, it is necessary to import ::abs() [what should have
been std::abs()] into namespace boost under MSVC, in order to let MSVC resolve
abs(int) correctly.

Template Instantiation

I am still not 100% sure what would be needed to ensure that we could
instantiate rational<> with a class (in namespace pfm) pfm::bigint, where there
was a defined pfm::abs(pfm::bigint). I am pretty sure that as long as we avoid
friend templates, we're OK on compilers which implement Koenig lookup. But what
we'd need to work around a lack of Koenig lookup in this case, I really don't
know. Probably import pfm::abs into the boost namespace, with an abomination

    namespace boost {
        using pfm::abs;
        // possibly even using pfm::bigint; !!

in *client* code.


For serious use of non-member functions in the interface of a class, Koenig
lookup is pretty important. It is possible to work around on the client side,
but for implementers of template classes, it is pretty much essential to making
a class work with a general type parameter.

Friend templates are very fragile at present (OK, so this is from a sample of 2
compilers...) and probably not a good idea for production code. You can work
around this - either the necessary class innards are already exposed, as in
rational<>, or you can expose a "helper" member function and use that to define
the non-member.

MSVC name lookup and overload resolution is badly broken (and as is usual for MS
products, not well documented, so you can only guess what is actually happening
based on experiment). It simply isn't possible to write properly generic code on
MSVC as a consequence of this. But it is possible to cover the key cases, at the
cost of some nasty workarounds.

Finally, a proposal

Could I suggest the addition of a something to boost/config.hpp to cover the
issues raised here.

I can only see two possible approaches, neither ideal, but both better than
spreading _MSC_VER checks round implementation files.

1. Just accept that MSVC-specific hacks are precisely that. Add a BOOST_MSVC
define, which is more selective than _MSC_VER (eg, we don't define it on
MetroWerks) which implementers can use when they finally give up trying to get
MSVC to act like a C++ compiler :-)

2. Clarify the nature of the above hacks, and give them a specific name (eg,
BOOST_NONMEMBER_FUNCTION_LOOKUP_BROKEN) and document the exact stylised
workarounds which are intended to be protected with this (import global/std
functions into the boost namespace).

Logically, (2) is better, but I have a feeling that the bugs in MSVC are
ill-defined enough even now, that it could turn into just a more verbose version
of (1) - as no other compiler would have precisely the same set of bugs anyway.
So I'd argue that (1) is probably more sensible, and requires less maintenance
in the long run.

Unless/until such an addition is made to config.hpp, I'll add a conditional
definition to the top of rational.hpp.

Sorry this turned out so long, I hope it is of some use to someone.


Boost list run by bdawes at, gregod at, cpdaniel at, john at