Boost logo

Boost :

From: Paul Mensonides (pmenso57_at_[hidden])
Date: 2005-08-17 06:07:19


> -----Original Message-----
> From: boost-bounces_at_[hidden]
> [mailto:boost-bounces_at_[hidden]] On Behalf Of Mat Marcus

> Here is a simplified version of the context in which the
> problem seems to crop up.
>
> //---- operator.hpp
>
> template <class T>
> struct is_foo {/*...*/}; // some appropriate compile time test
>
> template <class T>
> typename boost::enable_if<boost::is_foo<T>, T>::type
> operator| (T a, T b);
>
>
> //---- client.cpp
>
> #include "operator.hpp"
>
> So far things are ok. But now suppose that I am using gcc 4.0
> the client later includes a new header system_header.hpp.
>
> //---system_header.hpp
>
> enum { kCaseInsenstiveMask = 1L << 1,
> kReverseOrderMask = 1L << 2,
> kOverrideMask = 1L << 3};
>
> enum { kTheWorks = kCaseInsenstiveMask | kReverseOrderMask |
> kOverrideMask };
>
> That is:
>
> 1) If gcc 4.0 is correct, then I currently conclude that
> operator.hpp should break in the presence of system_header.hpp

The standard doesn't say anything about this directly, AFAICT. Same thing with
types with internal linkage (such as function-local types). Neither is in the
explicit SFINAE list (though it should be). So, I don't know for sure if gcc is
correct.

> 2) I would like to "repair" operator| , and to make it robust
> in the presence of constructs like those in system_header.hpp

> 3) I asked whether anonmyous enum detection was possible in
> support of goal 2.

There is nothing that you can do here. If the correct behavior is to have type
deduction (or substitution) fail for any anonymous type or type with internal
linkage, then it would be posssible to detect whether something was an anonymous
type, but only directly. I.e. you couldn't package it in a metafunction. OTOH,
you wouldn't need to detect it, if that was the case.

[Diatribe: I've mentioned this before (and I know you're referring to "system
header" here), but use of enumerations has to be done carefully because they are
unique types. Because they are unique types, operators can be overloaded on
them--including template operators--which can turn constant expressions into
runtime expressions. They can also cause unnecessary template bloat. Const
variables are safer (or static const inside classes).]

Getting back to the issue at hand... Basically I think that gcc is wrong--bear
with me while I explain why I think that. Before overload resolution occurs,
entities are looked up (via normal lookup and ADL). All names that denote
function templates undergo argument deduction--which may fail (SFINAE). All
normal functions and all templates where argument deduction succeeds become part
of the candidate set (including functions with too few or too many arguments for
the call provided they somehow get through argument deduction). Overload
resolution then occurs on that candidate set. (Overload resolution discards
those functions that require too many or too few arguments.) This is what
strictly happens according to the standard. (A lot of compilers, if not all,
cheat heavily in this area by blurring together argument deduction and overload
resolution.) Basically, what it comes down to is that this...

   template<class T> struct A { };

   template<class T> A<T> operator|(A<T>, A<T>);

...is no different than...

   template<class T> T operator|(T, T);

...as far as argument deduction is concerned. Even though the compiler can
cheat and know that an enum cannot match A<T>, it is supposed to attempt
argument deduction. The problem here is that there is no way for argument
deduction to succeed with an anonymous type because an anonymous type cannot be
bound to a template argument--meaning that argument deduction must fail before
it even tries. If gcc is correct, any visible template operator| at all should
cause an error in the enumeration initializers--which is completely ridiculous
and definitely not the intended behavior. Therefore, I think that gcc is wrong.

Given that this is the case with gcc (regardless of whether it is correct), I
don't think there is any way that you can protect your operator|--by the time
you can do anything with T, it is too late. The only solution, AFAICS, is to
name the enumeration type, which will cause SFINAE to kick in and discard your
operator| when it does the enumerator initializers.

Regards,
Paul Mensonides


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