tags:

views:

590

answers:

6

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?

+1  A: 

Boost traits has something for that : is_base_of

David Pierre
A: 

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.

A: 

I'm interested in doing this 'from first principles' as an educational curiosity. However, I will look at the Boost libraries.

In any case, I don't think is_base_of is any help - it does exactly the same as the SUPERSUBCLASS macro...

A: 

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)
+1  A: 

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

Nicola Bonelli
Thanks muchly - this is just what I was after.
+1  A: 

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_);
}
Konrad Rudolph
This method will lead to an ambiguous overloading. You should let the compiler drop the second method (the default case) when is_base_of<> metafunction succeeds.
Nicola Bonelli
I in code to drop the default case when the first one matched. Not sure if it's right though
1800 INFORMATION