Boost logo

Boost :

Subject: Re: [boost] [Review] GGL review starts today, November 5th
From: Barend Gehrels (barend_at_[hidden])
Date: 2009-11-08 08:34:28


Hi Vicente,

Thanks for diving into our library.

> Note that I have just read the introduction of the documentation and I find the it quite clear.
>

Good to hear.

> Some words about SNIFAE and partial specialization:
>
I've no problem to rephrase the documentation-section about SFINAE or
even to omit it. The section documents our choice for tag dispatching,
but it might be not necessary to do it in this detail. I've also no
problems with SFINAE in general, it is just a preference for usage in
more complex libraries like GGL.

We've used SFINAE in the past, for about half a year, in GGL. However,
we did have much problems with it, getting it all right and compiling it
on all compilers. We especially did have troubles with the boost concept
checking in combination with sfinae (maybe I should add that). Dave
Abrahams wrote about this (I quote here from a mail in january 2009):

>
> > Barend: (...) This works perfectly and distinguishes all geometry types at compile
> > time, as long as the library or the developer provides the meta function
> > geometry_type<T>::type which is currently implemented as an enumeration
> > of TYPE_POINT, etc.
>
>
> Dave: Seems like you might be better off using tag dispatching...
We went over to tag dispatching and all those problems disappeared.

Maybe I should add (if the section is not going to be omitted) that the
combination of SFINAE and the BCCL using boost-concept-requires was
quite difficult. I Just rechecked some things and I think this still is
the case. I checked using a small source with things as "apple", "pear",
"banana", so you'll see them in my answers.

> [snipped] Even if you neded SFINAE, and if I'm not wrong enable_if can also be applied to structs. From enable_if docummentation "
> Enabling template class specializations
> Class template specializations can be enabled or disabled with enable_if. One extra template parameter needs to be added for the enabler expressions. This parameter has the default value void. For example:
> template <class T, class Enable = void>
> class A { ... };
>
> template <class T>
> class A<T, typename enable_if<is_integral<T> >::type> { ... };
>
> template <class T>
> class A<T, typename enable_if<is_float<T> >::type> { ... };"
>

That is indeed possible, just tried it with enable_if.

But, technically speaking, I don't think this is SFINAE. I think it is
partial specialization, and specialization is done here on
enable_if<...>::type, finding matching candidates and if no one is
found, using the unspecialized class. We don't have the real
"Substitution Failure" here, where one or more overloads are discarded
from the candidate set based on failing substitution.

So these classes with enable_if work about the same way as tag
dispatching does, though no tag is necessary. But also here I prefer tag
dispatching because (my personal opinion) I find it resulting in more
readable code.

>> *gives often compiler troubles and headaches: if a user makes an error somewhere, the compiler will not select any of the methods, and/or it will give completely incomprehensible error listings, just because of this SFINAE"
>>
>
> I thought that this was and advantage not a liability. The message is short: method not found.
> What errors do you get when a user makes an error somewhere with tag dispatching? Are they clearer?
>
The messages you get depend on the type or error, in both cases.

When I try to use a function based on sfinae, when there is no match,
is: the message I get
- from MSVC is: Failed to specialize function template..., for all
overloads.
- from gcc is: no matching function for call to.... (one)

On tag dispatching you'll get:
MSVC: 'type' : is not a member of 'tag<T>' with [T=banana]
gcc, similar

or (if the banana-tag is implemented)

MSVC: 'apply' : is not a member of 'dispatch<T>' with [T=tag<banana>::type]
gcc: 'apply' is not a member of 'dispatch<banana_tag>'

sfinae is based on overloads, and the problem I encountered with it (and
with its messages) that in case of errors I often don't understand where
the error originates from. I remember having to number several times all
overloads (of e.g. "eat") to e.g. eat1, eat2, eat3, etc, to see what is
actually going wrong and why the overload was not called in that
specific case.

With tag dispatching you can also get pages of messages, but it is more
clear where they come from, e.g. a missing specialization, a wrong input
type.

>
>> * does not support partial specializations because it is a function. The tag-dispatching function is of course also not supporting that, but it forwards its call to the dispatch struct where partial specializations (and member template functions) are possible. The SFINAE could do that as well but then: why not just add one tag more and have tag dispatching instead?"
>>
>
> Could you clarify which is the tag you add?
>

If I understand your question: we're adding a geometry-tag. So for a
point it specializes to point_tag, etc.

What is meant by this section is that for some cases, code is similar.
For example the "num_points" algorithm, the number of points of a
linestring is the same as the number of points of a linear ring. The
dispatch function can specialize partially on this similarity. I now
realize, by just doing some re-checking that for SFINAE you can do the
same, using SFINAE on a specific property to define an overload which
applies to more than one geometry.

So this sentence probably can go, it is possible in both cases.

>
>> * is a trick to deceive the compiler. "As a language behavior it was designed to avoid programs becoming ill-formed" (http://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error), while tag dispatching is based on specialization, a core feature of C++
>> * looks more ugly
>>
>
> You know that this is a matter of taste, and the way you do force the user to share your taste.
>
I can rephrase this. What I mean is that a function

template <typename T>
void eat(T const& fruit)
{
    // tag dispatch on apple-tag
}

looks better than:

template <typename T>
void eat(T const& fruit
#ifdef DOXYGEN_SHOULD_SKIP_THIS
    , typename boost::enable_if<typename is_apple<T>::type>::type* = NULL
#endif
    )
{
    // (call) apple implementation
}

Maybe I should state "makes the main free function declarations shorter"
to be more objective.

>
>> *is simply not necessary because we have tag dispatching :-)
>>
>
> Not necessary do not meens that it is not good.
>
Right, I agree.

I still find (but that is an opinion indeed) that using tag dispatching
results in clean and readable code, and cause less compiler troubles.
Besides that, as far as I can tell, in combination with
BOOST_CONCEPT_REQUIRES, a solution based on SFINAE alone is not possible
using SFINAE. This set does not compile:
template <typename T>
BOOST_CONCEPT_REQUIRES( ((concept::Apple<T> )), (void) )
eat(T const& fruit, typename boost::enable_if<typename
is_apple<T>::type>::type* = NULL)
{...}

template <typename T>
BOOST_CONCEPT_REQUIRES( ((concept::Pear<T> )), (void) )
eat(T const& fruit, typename boost::enable_if<typename
is_pear<T>::type>::type* = NULL)
{...}

With messages: 'sourness' : is not a member of 'pear' or in gcc: no type
named 'sourness' in struct pear
But the expected type "sourness" was defined for apple, in this case,
and checked by the apple concept... the code IS right.... Combinations
are listed in the errors (so apple/pear, pear/apple), if we add more
fruit we would get much more.

Using tag dispatching, it is not possible to phrase it like this,
because there is only one free function, we cannot check the Apple
concept here. But concepts can be dispatched as well, resulting in:
template <typename T>
BOOST_CONCEPT_REQUIRES( (( dispatched_concept<typename tag<T>::type, T>
)), (void) )
eat(T const& fruit)
{...}

and this compiles.

Tag dispatching of the concept, or a solution using enable_if in a
struct, can also be used in the SFINAE solution but... then it is not
pure SFINAE (if I'm right), it is (at least partly) based on tag
dispatching or other partial specializations. But OK, you can combine
SFINAE and tags, of course that is possible.

By the way, we're currently using BOOST_CONCEPT_ASSERT (where concept
checking is dispatched), but as found out here, we have the choice

> [snipped] Do you think that this could be also applied to the GGL library at least for the concepts?
>
Interesting, I'll react on the convert_to in another e-mail later.

Regards, Barend


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