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 );
}