views:

399

answers:

4

Is it possible to establish a set of templated function pointers, without the hassle of doing so manually? Here's an example to illustrate what the heck I'm talking about.

Let's say I have a frequently-called function "write" of which I have two implementations (write0 and write1) that I'd like to be able to switch between dynamically. These write functions are templated on the argument type. One way to do this is to just have a templated front-end function write() which internally uses an if statement.

This turns out to be fast enough for my needs, but now I was left wondering if I can do the same using function pointers (just for fun). The problem with this approach is that setting up the function pointers is a hassle. Are there any other ways to essentially achieve the ideal of write() but without the conditional (direct static dispatch)?

(Other "rules": I can't change the Msg classes to have write() methods, and I can't change the use site code to replace Msgs with adaptors for Msgs.)

FWIW, I found this article basically saying the same thing I'm saying here.

#include <iostream>
using namespace std;

template<typename T> void write0(T msg) { cout << "write0: " << msg.name() << endl; }
template<typename T> void write1(T msg) { cout << "write1: " << msg.name() << endl; }

// This isn't so bad, since it's just a conditional (which the processor will
// likely predict correctly most of the time).
bool use_write0;
template<typename T> void write(T msg) { if (use_write0) write0(msg); else write1(msg); }

struct MsgA { const char *name() { return "MsgA"; } };
struct MsgB { const char *name() { return "MsgB"; } };
struct MsgC { const char *name() { return "MsgC"; } };
struct MsgD { const char *name() { return "MsgD"; } };

// This doesn't work: templates may not be virtual.
#if 0
struct Writer { template<typename T> virtual void write(T msg) = 0; };
struct Writer0 { template<typename T> virtual void write(T msg) { cout << "write0: " << msg.name() << endl; } };
struct Writer1 { template<typename T> virtual void write(T msg) { cout << "write0: " << msg.name() << endl; } };
#endif

int main(int argc, char **argv) {
  use_write0 = argc == 1;

  // I can do this:
  write(MsgA());

  // Can I achieve the following without the verbosity (manual setup, named
  // template instantiations, etc.)?
  void (*pwriteA)(MsgA) = use_write0 ? (void(*)(MsgA)) write0<MsgA> : (void(*)(MsgA)) write1<MsgA>;
  void (*pwriteB)(MsgB) = use_write0 ? (void(*)(MsgB)) write0<MsgB> : (void(*)(MsgB)) write1<MsgB>;
  void (*pwriteC)(MsgC) = use_write0 ? (void(*)(MsgC)) write0<MsgC> : (void(*)(MsgC)) write1<MsgC>;
  void (*pwriteD)(MsgD) = use_write0 ? (void(*)(MsgD)) write0<MsgD> : (void(*)(MsgD)) write1<MsgD>;
  pwriteA(MsgA());
  pwriteB(MsgB());
  pwriteC(MsgC());
  pwriteD(MsgD());

  return 0;
}
+3  A: 

If you want to switch logging functions back and forth while the program runs, I think you have to manually set the function pointer for each type.

If it's enough to just choose the logging function at startup, it can be done in a fully generic way without even knowing for which types the function will be called later:

// writer functions
template<typename T> void write0(T msg) { std::cout << 0; };
template<typename T> void write1(T msg) { std::cout << 1; };

// global flag
bool use_write0;

// function pointers for all types
template<typename T>
struct dispatch {
   typedef void (*write_t)(T);
   static write_t ptr;
};

// main write function
template<typename T>
inline void write(T msg) {
   (*dispatch<T>::ptr)(msg);
}

// the fun part
template<typename T>
void autoinit(T msg) {
   if (use_write0)
      dispatch<T>::ptr = &write0<T>;
   else
      dispatch<T>::ptr = &write1<T>;
   // call again for dispatch to correct function
   write(msg);
}

// initialization
template<typename T>
typename dispatch<T>::write_t dispatch<T>::ptr = &autoinit<T>;

// usage example
int main(int argc, char **argv) {
   use_write0 = (argc == 1);
   write("abc");
   return 0;
}

For each type T the first call to write<T>() decides which writing function should be used. Later calls then directly use the function pointer to that function.

sth
Clever. This is precisely what I sought - how to achieve template-specialized storage (for the values of the function pointers). The key is using templatized static class fields! Plus automagic lazy initialization of those fields - brilliant. Thanks.
Yang
+1  A: 

You could also use Don Clugston's FastDelegates header. Generates no runtime overhead whatsoever and truly object-oriented delegates. While the syntax for using them is not perfect, it is a bit simpler than fiddling with raw function pointers.

Adrian Grigore
Unless I'm mistaken, I don't believe FastDelegates are useful here, as I'm not trying to bind to member functions. I think the core issue is template-specialized storage (for the value of the function pointers).
Yang
A: 

Why don't you use an array of function pointers?

#include <iostream>
using namespace std;

template<typename T> void write0(T msg) { cout << "write0: " << msg.name() << endl; }
template<typename T> void write1(T msg) { cout << "write1: " << msg.name() << endl; }

template<typename T> struct WriteSelector
{
    static void(* const s_functions[])(T msg);
};
template<typename T> void(* const WriteSelector<T>::s_functions[])(T msg)=
{
    &write0<T>,
    &write1<T>
};

unsigned write_index=0;
template<typename T> void write(T msg)
{
    WriteSelector<T>::s_functions[write_index](msg);
}


struct MsgA { const char *name() { return "MsgA"; } };
struct MsgB { const char *name() { return "MsgB"; } };
struct MsgC { const char *name() { return "MsgC"; } };
struct MsgD { const char *name() { return "MsgD"; } };

void Test()
{
    write(MsgA());
    write(MsgB());
    write(MsgC());
    write(MsgD());
}

int main()
{
    Test();
    write_index=1;
    Test();
    return 0;
}
Luppy
This is equivalent (but generalize-able to >2 choices). Also, fn ptrs (and e.g. vtable dispatch) exhibit the similar performance as branches. In both cases the pipeline stalls before it knows the next instruction (branches wait slightly longer), but branches have only 2 possibilities to predict.
Yang
A: 

There are two axises of variation in writing: the write0/write1 choice and the MsgA/B/C.... choice.

Conceptually that means you need NxM implementations of a write function. Of course, if a write implementation is added, or a message type is added, this leads to resp. M or N extra functions to be added.

For both axises you can choose whether to implement them using static or dynamic polymorphism. The static polymorphism can be done using templates or using function overrides.

It could be done by creating a N element class hierarchy with M write functions in each class. But it would soon become a maintenance nightmare. Unless the message content is also runtime polymorphic. But the question is about static polymorphism for the messages.

Since runtime polymorphism is ruled out because of too elaborate (and you can't have a template function virtual, which would decrease the verbosity of overrides), we need to implement a little type-dispatching routine, converting runtime information into compile-time information.

More specifically: templatize the main action (in the example called Tmain) with the writer-to-use, and call it with the right template argument from the 'real' main.

This omits the use of a 'global' choice variable, yet is object-oriented and concise.

 // twodimensionalpolymorph.cpp
 //

 #include <iostream>

 using namespace std;

 class Write0 {
  public: 
  template< typename tMsg > 
  void operator()( /*const*/ tMsg& msg ) { cout << "write0: " << msg.name() << endl; };
 };

 class Write1 {
  public: 
  template< typename tMsg > 
  void operator()( /*const*/ tMsg& msg ) { cout << "write1: "<< msg.name() << endl; };
 };

 struct MsgA { const char *name() { return "MsgA"; } };
 struct MsgB { const char *name() { return "MsgB"; } };
 struct MsgC { const char *name() { return "MsgC"; } };
 struct MsgD { const char *name() { return "MsgD"; } };

 // the Tmain does the real action
 //
 template< typename Writer >
 int Tmain( Writer& write, int argc, char** args ) {

  write( MsgA() );
  write( MsgB() );
  write( MsgB() );
  write( MsgD() );

  return 0;
 }

 // the main merely chooses the writer to use
 //
 int main( int argc, char** args ) {

  if( argc==1 )
   return Tmain( Write0(), argc, args);
  else
   return Tmain( Write1(), argc, args);

 }
xtofl
Nothing was ruled out for being "too elaborate;" templated virtuals are simply not valid C++.Your approach is interesting and would work for some cases, but I'm calling write() from all over the program, and would like to avoid propagating the template parameter everywhere.
Yang
BTW, minor bug in your code: you can't have rvalue refs (yet).
Yang
I didn't state it correctly: if you want two 'dimensions' of variation (i.e. the write type and the message type) without templates, you end up with NxM implementations. That's elaborate, but would do the trick: capture function pointers, and be flexible at run time.
xtofl
And thanks for pointing out the virtual template thing. And apparently I need to get upto speed regarding the rvalues (silly excuse: VC++ doesn't complain:( )
xtofl