1

I have a class template ID, which currently uses an object's address as its identifier.

I would like to have a specialisation of this class template so that if T has a key_type defined, I will call get_key() on the object and use that, instead, as the identifier. Of course, I will also store that value, so there will be a high degree of overlap with the original implementation.

What's the best way to achieve this, while maximizing code reuse?

In the code below, I would like ID<Bar> to automagically use std::string in its guts rather than Bar const*.

I tried looking into SFINAE, as a way of switching on whether or not T::key_type is defined, but I could only find examples for function templates.

CODE

#include <string>
#include <set>
#include <cassert>

template<typename T>
class ID
{
private:
    T const* m_id;
public:
    ID( T const& t ) :
        m_id( &t )
    { }
    ID( ID const& rhs ) :
        m_id( rhs.m_id )
    { }
    ~ID() { }
    ID& operator=( ID const& rhs )
    {
        if ( &rhs!=this )
            m_id = rhs.m_id;
        return *this;
    }
public:
    bool operator==( ID const& rhs ) const { return m_id==rhs.m_id; }
    bool operator!=( ID const& rhs ) const { return !(*this==rhs);  }
    bool operator<( ID const& rhs ) const  { return m_id<rhs.m_id;  }
    bool operator<=( ID const& rhs ) const { return m_id<=rhs.m_id; }
    bool operator>( ID const& rhs ) const  { return m_id>rhs.m_id;  }
    bool operator>=( ID const& rhs ) const { return m_id>=rhs.m_id; }
};

// -----------------------------------------------------------------------------

struct Foo { };
struct Bar { 
    using key_type = std::string;

    std::string  m_key;
    Bar( std::string const& key ) : m_key( key ) { }
};

int main( int argc, char* argv[] )
{
    Foo f,g;

    ID<Foo> id_f( f ), id_g( g );
    assert( id_f!=id_g );

    std::set<ID<Foo>>  s;
    s.insert( f );
    s.insert( g );

    assert( s.size()==2u );

    Bar h( "abc" ), i( "abc" );
    std::set<ID<Bar>>  s2;
    s2.insert( h );
    s2.insert( i );
    assert( s2.size()==1u );
}

I was able to code up a solution based on Seth Carnegie's answer below.

SOLUTION

#include <string>
#include <set>
#include <cassert>

template<typename T>
struct void_ {
    using type = void;
};

// -----------------------------------------------------------------------------

template<typename T, typename = void>
struct ptr_or_key_type {
    using type = T const*;               // our default key_type : ptr
    static type get_key( T const& t ) { return &t; }
};

template<typename T>
struct ptr_or_key_type<T, typename void_<typename T::key_type>::type> {
    using type = typename T::key_type;   // the specialised key_type
    static type get_key( T const& t ) { return t.get_key(); }
};

// -----------------------------------------------------------------------------

template<typename T>
class ID
{
private:
  typename ptr_or_key_type<T>::type m_id;

public:
    ID( T const& t ) :
        m_id( ptr_or_key_type<T>::get_key( t ))
    { }
    ID( ID const& rhs ) :
        m_id( rhs.m_id )
    { }
    ~ID() { }
    ID& operator=( ID const& rhs )
    {
        if ( &rhs!=this )
            m_id = rhs.m_id;
        return *this;
    }
public:
    bool operator==( ID const& rhs ) const { return m_id==rhs.m_id; }
    bool operator!=( ID const& rhs ) const { return !(*this==rhs);  }
    bool operator<( ID const& rhs ) const  { return m_id<rhs.m_id;  }
    bool operator<=( ID const& rhs ) const { return m_id<=rhs.m_id; }
    bool operator>( ID const& rhs ) const  { return m_id>rhs.m_id;  }
    bool operator>=( ID const& rhs ) const { return m_id>=rhs.m_id; }
};

// -----------------------------------------------------------------------------

struct Foo { };
struct Bar { 
    using key_type = std::string;

    std::string  m_key;
    Bar( std::string const& key ) : m_key( key ) { }
    std::string const& get_key() const { return m_key; }
};

int main( int argc, char* argv[] )
{
    Foo f,g;

    ID<Foo> id_f( f ), id_g( g );
    assert( id_f!=id_g );

    std::set<ID<Foo>>  s;
    s.insert( f );
    s.insert( g );

    assert( s.size()==2u );

    Bar h( "abc" ), i( "abc" );
    std::set<ID<Bar>>  s2;
    s2.insert( h );
    s2.insert( i );
    assert( s2.size()==1u );
}
4

1 回答 1

4

You can use SFINAE a small combo of traits classes:

template<typename T>
struct void_ {
    using type = void;
};

template<typename T, typename = void>
struct key_type_or_id {
     using type = T const*; // Our default key_type
};

template<typename T>
struct key_type_or_id<T, typename void_<typename T::key_type>::type> {
    using type = typename T::key_type; // The specialised key_type
};

...

template<typename T>
class ID
{
private:
    typename key_type_or_id<T>::type m_id;

...
于 2013-02-06T06:15:54.777 回答