Boost logo

Boost :

From: Simonson, Lucanus J (lucanus.j.simonson_at_[hidden])
Date: 2008-07-25 14:53:00


Bruno wrote:
>I'm not sure your problem is the same as mine. My problem is that the
>result type gets compiled even though there's absolutely no ambiguity
>between the 2 functions. There's no need to do any SFINAE
>discrimination to conclude that foo<T>& matches much better that T&
>for this call. I'd be happy if the compiler waited to have a true
>ambiguity to solve before compiling any return type but things are
>like that, I can't rewrite the norm.

>Your problem is rather a lack of strength of SFINAE. As you say, the
>SFINAE principle seems to be only applied on a first-level check. If
>the substitution fails at a more nested level, it's an error. To be
>honest, this surprises me. Don't know what the norm says about that...

Yes, I realized that as I read the rest of your thread. I assumed you
were asking why SFINAE didn't prevent the compiler error, as did a
couple other people at first, because I had just finished some outside
the box programming to deal with the limitation of SFINAE not allowing
me to specify default behavior for my metafunctions. I was surprised by
the solution I came up with, and I'd like to share it with you.

My goal, if you recall, was to provide generic +, &, |, ^, *, -, +=, &=,
|=, *=, &= and -= operators for performing 2D geometric set operations.
The arguments that these operators should take could be any stl
container of objects that model an applicable geometry concept, my own
data structure that encapsulates the input to the algorithm, or the
operator template type that the operators return.

The declaration for the operator+ looks like so:

template <typename geometry_type_1, typename geometry_type_2>
polygon_set_view<
  typename polygon_set_traits<geometry_type_1>::operator_arg_type,
  typename polygon_set_traits<geometry_type_2>::operator_arg_type,
  boolean_op::BinaryOr>
operator+(const geometry_type_1& lvalue, const geometry_type_2& rvalue)

In the declaration of the operator I'm depending on SFINAE to prevent
the template from being instantiated for any type T where the
polygon_set_traits<T> does not declare an operator_arg_type. Because
substitution must fail in the first level check I cannot declare a
default polygon_set_traits and have to instead specialize the
polygon_set_traits struct for every valid argument type. For my own
types, such as the operator template I can use a templated
specialization:

template <typename T>
struct polygon_set_traits {};

template <typename T1, typename T2, typename T3>
struct polygon_set_traits<polygon_set_view<T1, T2, T3> > {
        typedef polygon_set_view<T1, T2, T3> operator_arg_type;
      ...
};

The specialization includes several additional typedefs and functions
that define the minimal sufficient interface to use the type as a
polygon set for the purposes of polygon set operations. It is about 30
lines of code to specialize the polygon_set_traits for each type. I
needed to specialize it for any container of geometry objects, but
because it would break down my SFINAE protection I can't specialize it
for container of any element type:

template <typename element_type>
struct polygon_set_traits<std::vector<element_type> > { ... };

because that would allow any vector to instantiate the operator leading
to syntax errors in the case were a correct generic operator for a
vector of that type existed in some other library. That means I have to
specialize polygon_set_traits for each container of each geometry type
explicitly. However, because containers conform to a standard set of
behaviors, and my geometry concepts allow me to treat different types of
geometry objects generically the specialization for each such container
can be made to look identical to that of any other. I didn't want that
code duplication, but how could I avoid it without templating the
element type? A macro? I found a better way.

I created a wrapper class that wraps a container of geometry objects:

template <typename T>
class polygon_set_wrapper {
private:
  T& t_;
  ...
};

and created a generic specialization of the polygon set traits for the
wrapper:

template <typename T>
struct polygon_set_traits<polygon_set_wrapper<T> > {
  typedef T operator_arg_type;
...
};

That implemented all the required traits for a generic container of
geometry objects such that it worked for any container of geometry
objects. I then inherit the explicit specialization of
polygon_set_traits for specific containers of geometry objects from that
one like so:

template <typename T>
struct polygon_set_traits<std::vector<rectangle_data<T> > > : public
polygon_set_traits<polygon_set_wrapper<std::vector<rectangle_data<T> > >
> {};

which allows me to specify a generic category of traits and assign it to
specific types using inheritance of specialization to meet the
requirement that the template used for SFINAE is narrowly defined and
substitution fails at the first level of compilation.

I found this use of inheritance between specializations of a
class/struct surprising in the sense that it is both supported and
useful. I thought other people on the list might find it interesting.

Luke


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