Reputation: 1627
I am writing a game and I use a ResourceHolder template class which provides functions to load and access a resource(texture, music, font...). It works great for Texture or Font classes because they have the same interface for loading(their functions are called "loadFromFile") but the Music class works a bit differently and it has an "openFromFile" function instead (I use a library called SFML).
template <typename Resource, typename Identifier>
void ResourceHolder<Resource, Identifier>::load(Identifier id,
const std::string & fileName)
{
std::unique_ptr<Resource> resource(new Resource());
resource->loadFromFile(fileName);
}
I tried an if-else approach:
if (typeid(id).name() == "Resources::MusicType")
resource->openFromFile(fileName);
else
resource->loadFromFile(fileName);
But compiler throws an error when compiling the Texture version of the template since it doesn't have openFromFile method. I also considered passing the function as a third template parameter but I'd like to know whether there is a better solution. Thanks.
Upvotes: 1
Views: 212
Reputation: 217135
with following type_traits:
#include <cstdint>
#define DEFINE_HAS_SIGNATURE(traitsName, funcName, signature) \
template <typename U> \
class traitsName \
{ \
private: \
template<typename T, T> struct helper; \
template<typename T> \
static std::uint8_t check(helper<signature, &funcName>*); \
template<typename T> static std::uint16_t check(...); \
public: \
static \
constexpr bool value = sizeof(check<U>(0)) == sizeof(std::uint8_t); \
}
DEFINE_HAS_SIGNATURE(has_loadFromFile, T::loadFromFile, void (T::*)(const std::string&));
You may use tag dispatching:
template <typename Resource, typename Identifier>
class ResourceHolder
{
struct load_tag{};
struct open_tag{};
public:
void load(Identifier id,
const std::string& fileName)
{
load(id, fileName,
typename std::conditional<has_loadFromFile<Resource>::value,
load_tag, open_tag>::type{});
}
private:
void load(Identifier id,
const std::string& fileName,
load_tag)
{
std::unique_ptr<Resource> resource(new Resource());
resource->loadFromFile(fileName);
}
void load(Identifier id,
const std::string& fileName,
open_tag)
{
std::unique_ptr<Resource> resource(new Resource());
resource->openFromFile(fileName);
}
};
or SFINAE:
template <typename Resource, typename Identifier>
class ResourceHolder
{
public:
template <typename T = Resource>
typename std::enable_if<has_loadFromFile<T>::value>::type
load(Identifier id,
const std::string& fileName)
{
std::unique_ptr<Resource> resource(new Resource());
resource->loadFromFile(fileName);
}
template <typename T = Resource>
typename std::enable_if<!has_loadFromFile<T>::value>::type
load(Identifier id,
const std::string& fileName)
{
std::unique_ptr<Resource> resource(new Resource());
resource->openFromFile(fileName);
}
};
Upvotes: 1
Reputation: 39013
Template Specialization is your friend.
Basically you can create a specialized function for specific types:
template <typename Resource>
void ResourceHolder<Resource, MusicType>::load(...)
I'm not sure this is the right way, as I'm not sure what's the difference between Resource
and Identifier
, and how you use them.
Upvotes: 2