Code: Select all
template<typename InputManager>
class Keyboard
{
public:
void Update ( InputManager& input_manager )
{
// Receive key presses
while ( !keys.empty ( ) )
{
auto key = keys.front ( );
input_manager.on_key_pressed ( key );
keys.pop ( );
}
}
private:
std::queue<char> keys;
};
However, if you look at any standard library implementation, they usually don't operate on the template type parameters directly. Most of the time, they'll do operations on them through a 'trait' class.
Code: Select all
template<typename pointer>
pointer const_iterator::operator-> ( ) const
{ // return pointer to class object
return ( pointer_traits<pointer>::pointer_to ( **this ) );
}
Traits are simple objects used to provide information on other types. This information can help determine the implementation of a class and how its algorithms will handle these types. Most of you would have likely been introduced to them through std::numeric_limits.
Code: Select all
const auto max_int = std::numeric_limits<int>::max ( );
const auto max_float = std::numeric_limits<float>::max ( );
const auto max_double = std::numeric_limits<double>::max ( );
Code: Select all
template<typename InputManager>
// Provides intellisense with an interface to work with
struct InputManagerTraits
{
static void on_key_pressed ( InputManager& input_manager, const char key )
{
input_manager.on_key_pressed ( key );
}
}
template<typename InputManager>
class Keyboard
{
public:
using MyInputManagerTraits = InputManagerTraits<InputManager>;
void Update ( InputManager& input_manager )
{
// Receive key presses
while ( !keys.empty ( ) )
{
auto key = keys.front ( );
MyInputManagerTraits::on_key_pressed ( input_manager, key );
keys.pop ( );
}
}
private:
std::queue<char> keys;
};
Traits can also extend these types by giving them functions and variables they don't have. This is a great way of implementing 'default settings' for your concept. Imagine if your InputManager concept has a type alias called Key that sets what the key it processes is. The actual InputManager types can either explicitly say what the Key is or can just leave it to the trait class to specify. The InputManagerTraits would simply default Key to char if the InputManager type it is given does not have a Key.
Code: Select all
template<typename InputManager, typename = void>
struct GetKeyType
{ // provide fallback if Type has no Key
using type = char;
};
template<typename InputManager>
struct GetKeyType<InputManager, std::void_t<typename InputManager::Key>>
{ // get Type::Key
using type = typename InputManager::Key;
};
template<typename InputManager>
struct InputManagerTraits
{
using Key = typename GetKeyType<InputManager>::type;
static void on_key_pressed ( InputManager& input_manager, const char key )
{
input_manager.on_key_pressed ( key );
}
};
Code: Select all
class Player
{
public:
void MoveUp ( )
{
// Move up
}
void MoveDown ( )
{
// Move down
}
void MoveLeft ( )
{
// Move left
}
void MoveRight ( )
{
// Move right
}
};
template<>
struct InputManagerTraits<Player>
{
using Key = char;
static void on_key_pressed ( Player& player, const char key )
{
if ( key == 'W' )
{
player.MoveUp ( );
}
else if ( key == 'S' )
{
player.MoveDown ( );
}
else if ( key == 'A' )
{
player.MoveLeft ( );
}
else if ( key == 'D' )
{
player.MoveRight ( );
}
}
};
I hope that this is enough to motivate you to use traits. Once you take a look at more metaprogramming code, you're going to see traits everywhere. Just jump into Visual Studio's implementation of the stl library and spot the places where they're used.