Reputation: 487
Following techniques from 'Modern C++ Design', I am implementing a persistence library with various compile-time optimisations. I would like the ability to dispatch a function to a templated member variable if that variable derives from a given class:
template<class T, template <class> class Manager = DefaultManager> class Data
{
private:
T *data_;
public:
void Dispatch()
{
if(SUPERSUBCLASS(Container, T))
{
data_->IKnowThisIsHere();
}
else
{
Manager<T>::SomeGenericFunction(data_);
}
}
}
Where SUPERSUBCLASS is a compile-time macro to determine object inheritance. Of course, this fails in all cases where T does to inherit from Container (or T is an intrinsic type etc etc) because the compiler rightly complains that IKnowThisIsHere() is not a data member, even though this code path will never be followed, as shown here after preprocessing with T = int:
private:
int *data_;
public:
void Dispatch()
{
if(false)
{
data_->IKnowThisIsHere();
Compiler clearly complains at this code, even though it will never get executed. A suggestion of using a dynamic_cast also does not work, as again a type conversion is attempted at compile time that is not possible (for example with T=double, std::string):
void Dispatch()
{
if(false)
{
dynamic_cast<Container*>(data_)->IKnowThisIsHere();
error: cannot dynamic_cast '((const Data<double, DefaultManager>*)this)->Data<double, DefaultManager>::data_' (of type 'double* const') to type 'class Container*' (source is not a pointer to class)
error: cannot dynamic_cast '((const Data<std::string, DefaultManager>*)this)->Da<sttad::string, DefaultManager>::data_' (of type 'struct std::string* const') to type 'class Container*' (source type is not polymorphic)
I really need to emulate (or indeed persuade!) having the compiler emit one set of code if T does inherit from Container, and another if it does not.
Any suggestions?
Upvotes: 5
Views: 4633
Reputation: 545618
You require a kind of compile-time if
. This then calls a function depending on which case is true
. This way, the compiler won't stumble upon code which it can't compile (because that is safely stored away in another function template that never gets instantiated).
There are several ways of realizing such a compile-time if
. The most common is to employ the SFINAE idiom: substitution failure is not an error. Boost's is_base_of
ist actually an instance of this idiom. To employ it correctly, you wouldn't write it in an if
expression but rather use it as the return type of your function.
Untested code:
void Dispatch()
{
myfunc(data_);
}
private:
// EDIT: disabled the default case where the specialisation matched
template <typename U>
typename enable_if_c<is_base_of<Container, U>::value, U>::type myfunc(U& data_) {
data_->IKnowThisIsHere();
}
template <typename U>
typename disable_if_c<is_base_of<Container, U>::value, U>::type myfunc(U& data_) { // default case
Manager<U>::SomeGenericFunction(data_);
}
Upvotes: 2
Reputation: 8287
Overloading can be useful to implement compile-time dispatching, as proposed by Alexandrescu in his book "Modern C++ Design".
You can use a class like this to transform at compile time a boolean or integer into a type:
template <bool n>
struct int2type
{ enum { value = n}; };
The following source code shows a possible application:
#include <iostream>
#define MACRO() true // <- macro used to dispatch
template <bool n>
struct int2type
{ enum { value = n }; };
void method(int2type<false>)
{ std::cout << __PRETTY_FUNCTION__ << std::endl; }
void method(int2type<true>)
{ std::cout << __PRETTY_FUNCTION__ << std::endl; }
int
main(int argc, char *argv[])
{
// MACRO() determines which function to call
//
method( int2type<MACRO()>());
return 0;
}
Of course what really makes the job is the MACRO() or a better implementation as a metafunction
Upvotes: 3
Reputation: 487
Unfortunately I've been through that too (and it is, also, a runtime call ;) ) The compiler complains if you pass in non polymorphic or class types, in a similar way to before:
error: cannot dynamic_cast '((const Data<double, DefaultManager>*)this)->Data<double, RawManager>::data_' (of type 'double* const') to type 'class Container*' (source is not a pointer to class)
or
error: cannot dynamic_cast '((const Data<std::string, DefaultRawManager>*)this)->Data<std::string, DefaultManager>::data_' (of type 'struct std::string* const') to type 'class Container*' (source type is not polymorphic)
Upvotes: 0
Reputation: 4510
Look into the boost template meta programming library. Also, depending on what you are trying to accomplish look at the boost serialization library, since it may already have what you need.
Upvotes: 0