Sfinae and specialization

Some days ago a had a little Aha effect about Sfinae (Substitution failure is not an error). I know about Sfinae for a while and I know about the enable_if library in the current standard and in the boost libraries, but I was not aware of all its usefulness. In detail I was working on a small set of C++ functions and classes for writing arbitrary geographic data objects into a KML file. The idea is to put all data dependent code for the KML output into a static class display_kml with only one static method. This class is then specialized for all data types and classes. Furthermore, this class can be specialized by the users of the code to work with their own geographic types. In principle, this works very nicely:

template< typename T > struct display_kml;
 
template< class Geom >
void writeKML( const string &filename , const Geom &g )
{
    ofstream fout( filename.c_str() );
    fout << "<kml>" << endl;
    fout << "<Document>" << endl;
    fout << "<Placemark>" << endl;
    display_kml< Geom >::display( g , fout );
    fout << "</Placemark>" << endl;
    fout << "</Document>" << endl;
    fout << "</kml>" << endl;
}

You can now specialize display_kml for your types. For example, I often work with std::array for representing a geographic point. The first entry is the x component and the second the y component. To use such an array with the KML writer one only has to specialize display_kml and everything works:

template<>
struct display_kml< std::array< double , 2 > >
{
    static void display( const std::array< double , 2 > &g , ostream &out )
    {
        out << "<Point>" << endl;
        out << "<coordinates>" << endl;
        out << g[0] << "," << g[1] << ",0.0 " << endl;
        out << "</coordinates>" << endl;
        out << "</Point>" << endl;
    }
};
 
int main( int argc , char *argv[] )
{
    array< double , 2 > point = {{ 1.0 , 1.5 }};
    writeKML( "point.dat" , point );
}

Introducing other types and geometries is also very easy; you only have to specialize display_kml. One particular interesting use case was to create KML writers for the types from Boost.Geometry. For example I specialized display_xml for the point type of Boost.Geometry. But then, I remembered that Boost.Geometry defines a concept for point types. Unfortunately, it is not possible to write an generic display_xml for all types modelling the point concept from Boost.Geometry. And this is the place where Sfinae comes into play.

I am not going to explain what Sfinae is, other people have done that already, take a look for example here, or here. What I want to say is, that you can easily use Sfinae to implement a generic display_xml for all types modelling the point concept. To do this, you have to introduce a second type parameter in the class definition which plays the role of the enabler:

template< typename T , typename Enabler = void > struct display_kml;

All existing code will still work and compile since the enabler has a default parameter. But this new display_kml version can now be used for all types implementing the Boost.Geometry point concept. To do so, one uses the std::enable_if meta-function from the current C++ standard in combination with a compile-time check if a boost::geometry::traits::tag exist and if this tag is the point_tag from Boost.Geometry. This point_tag is one of the requirements for the point concept, so if this one exist one can be pretty sure to have a Boost.Geometry point. The whole display_kml specialization for the point concept is then:

template< class Point >
struct display_kml
<
    Point ,
    typename std::enable_if
    <
        std::is_same
        <
            typename bg::traits::tag< Point >::type ,
            bg::point_tag 
        >::value
    >::type
>
{
    inline static void display( const Point &p , std::ostream &out )
    {
        out << "<Point>" << endl;
        out << "<coordinates>" << endl;
        out << bg::get< 0 >( p ) <<"," << bg::get< 1 >( p ) << ",0 " << endl;
        out << "</coordinates>" << endl;
        out << "<Point>" << endl;;
    }
};

The second template parameter is now used und it tries to apply the meta function enable_if. enable_if takes two parameters

std::enable_if< Cond , Ret = void >

a compile-time boolean value Cond and a type Ret. Ret has the default value void so it is not needed here. If Cond is statically evaluated to true it return Ret, otherwise the return value is not defined and a substitution failure occurs. But this failure does not result in a compilation error. This is exactly what is used in our case. If a tag does not exist or the tag is not the point_tag a substituion failure occurs and the compiler will look for other specializations. (Sure, if it does not find a specialization it will throw a real error.) std::is_same simply checks if two types are equal. In our case it checks if the tag is equal to point_tag.

We are done and we can now use all types modelling the point concept. You can apply the same technique for line strings, polygons, or segments from boost. You can also specialize for your custom point types. In summary one can say that a enabler template parameter in a generic class hierarchy does not hurt but adds the possiblity for specializing for a whole range of types.

2 thoughts on “Sfinae and specialization

Leave a Reply

Your email address will not be published. Required fields are marked *