views:

72

answers:

2

Say you have some target class with some methods on it:

class Subject
{
public:
  void voidReturn() { std::cout<<__FUNCTION__<<std::endl; }
  int  intReturn()  { std::cout<<__FUNCTION__<<std::endl; return 137; }
};

And a Value class (similar in concept to Boost.Any):

struct Value
{
  Value() {}
  Value( Value const & orig ) {}
  template< typename T > Value( T const & val ) {}
};

And I want to produce a Value object using a method from the Subject class:

Subject subject;
Value intval( subject.intReturn() );
Value voidVal( subject.voidReturn() );  // compilation error

I get the following errors in VC++2008:

error C2664: 'Value::Value(const Value &)' : cannot convert parameter 1 from 'void' to 'const Value &'
Expressions of type void cannot be converted to other types

and gcc 4.4.3:

/c/sandbox/dev/play/voidreturn/vr.cpp:67: error: invalid use of void expression

The context for this is when you want to use it inside a templated class:

template< typename Host, typename Signature > class Method;

// Specialization for signatures with no parameters
template< typename Host, typename Return >
class Method< Host, Return () >
{
public:
  typedef Return (Host::*MethodType)();
  Method( Host * host, MethodType method ) : m_Host(host), m_Method(method) {}

  Value operator()() { return Value( (m_Host->*m_Method)() ); }
private:
  Host       * m_Host;
  MethodType   m_Method;
};

Using this Method class on the method which returns something (namely intReturn) would look like:

Method< Subject, int () > intMeth( &subject, &Subject::intReturn );
Value intValue = intMeth();

However, doing this with the voidReturn method:

Method< Subject, void () > voidMeth( &subject, &Subject::voidReturn );
Value voidValue = voidMeth();

yields similar errors as above.

One solution is to further partially specialize Method for void return types:

template< typename Host >
class Method< Host, void () >
{
public:
  typedef void Return;
  typedef Return (Host::*MethodType)();
  Method( Host * host, MethodType method ) : m_Host(host), m_Method(method) {}

  Value operator()() { return (m_Host->*m_Method)(), Value(); }
private:
  Host       * m_Host;
  MethodType   m_Method;
};

Besides it just feeling ugly, I'm also wanting to specialize the Method class for X numbers of signature parameters, which already involves a lot of code duplication (hopefuly Boost.Preprocessor can help here), and then adding a specialization for void return types just doubles that duplication effort.

Is there anyway to avoid this second specialization for void return types?

+1  A: 

No, there is absolutely no way to pass a void. It is an irregularity in the language.

The function argument list (void) is translated as (). Bjarne prefers the latter to the former, and begrudgingly allowed the C convention as a very limited kind of syntactic sugar. You can't even substitute a typedef alias of void, and you certainly can't have any other arguments.

I personally think this is a bad idea. If you can write void(expr), then you should be able to "initialize" an anonymous argument of type void. If you could also write a function with an arbitrary number of void arguments, there would be a way to execute a number of expressions in unspecified order, which would express concurrency in a way.

As for handling different-sized argument lists (also known as variadic), see variadic templates in C++0x before you start trying to learn Boost Preprocessor.

Potatoswatter
Thanks, I was afraid of that. I thought I had seen something in boost.function where they were doing something similar but apparently not. And thanks for the tip on variadic templates, unfortunately I haven't upgraded my compilers (yet).
Chris
"A function argument list with one argument of type void is adjusted to be a list with no arguments." -> actually it's even more strict. It's this way in C99, but in C++ the replacement is purely syntactic: The sequence of tokens "(void)" denotes a zero-parameter list. But for example "(Void)" where "Void" denotes the void type is illegal in C++.
Johannes Schaub - litb
@Johannes: fixed.
Potatoswatter
+3  A: 

You could use Return and just specialize operator() handling. No need to duplicate the whole template.

// I think it's a shame if c++0x really gets rid of std::identity. It's soo useful!
template<typename> struct t2t { };

// Specialization for signatures with no parameters
template< typename Host, typename Return >
class Method< Host, Return () >
{
public:
  typedef Return (Host::*MethodType)();
  Method( Host * host, MethodType method ) : m_Host(host), m_Method(method) {}

  Value operator()() { return call(t2t<Return>()); }

private:
  Value call(t2t<void>) { return Value(); }

  template<typename T>
  Value call(t2t<T>) { return Value((m_Host->*m_Method)()); }

private:
  Host       * m_Host;
  MethodType   m_Method;
};
Johannes Schaub - litb
Potatoswatter
@Potatoswatter too bad :(
Johannes Schaub - litb
Yes, that's what I was looking for! I remember reading about Type2Type awhile ago by Alexandrescu but have since forgot about it.
Chris