возвращаемое значение void из функции, используемой в качестве входных данных для шаблонной функции, рассматривается как параметр

Скажем, у вас есть целевой класс с некоторыми методами:

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

И класс Value (аналогичный концепции Boost.Any):

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

И я хочу создать объект Value, используя метод класса Subject:

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

Я получаю следующие ошибки в 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

и gcc 4.4.3:

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

Контекст для этого — когда вы хотите использовать его внутри шаблонного класса:

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;
};

Использование этого класса Method в методе, который что-то возвращает (а именно, intReturn), будет выглядеть так:

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

Однако, делая это с помощью метода voidReturn:

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

выдает те же ошибки, что и выше.

Одним из решений является дальнейшая частичная специализация Method для возвращаемых типов void:

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;
};

Помимо того, что это просто ужасно, я также хочу специализировать класс Method для X номеров параметров подписи, что уже требует большого количества дублирования кода (надеюсь, Boost.Preprocessor может помочь здесь), а затем добавить специализацию для возвращаемых типов void просто удваивает усилия по дублированию.

Можно ли как-то избежать этой второй специализации для возвращаемых типов void?


person Skillcheck    schedule 06.09.2010    source источник
comment
+1. Я думаю, это очень хорошо написанный вопрос.   -  person Johannes Schaub - litb    schedule 11.09.2010


Ответы (2)


Вы можете использовать Return и просто специализировать обработку operator(). Нет необходимости дублировать весь шаблон.

// 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;
};
person Johannes Schaub - litb    schedule 11.09.2010
comment
… и причина избавления от identity заключается в том, что определение его operator() для использования ссылок rvalue вместо const& нарушило бы слишком много установленного кода! - person Potatoswatter; 11.09.2010
comment
Да, это то, что я искал! Я помню, как некоторое время назад читал Александреску о Type2Type, но с тех пор забыл об этом. - person Skillcheck; 29.09.2010

Нет, абсолютно невозможно передать void. Это нарушение языка.

Список аргументов функции (void) переводится как (). Бьярне предпочитает последнее первому и неохотно допускает соглашение C как очень ограниченный вид синтаксического сахара. Вы даже не можете заменить псевдоним typedef на void, и, конечно же, у вас не может быть никаких других аргументов.

Я лично считаю, что это плохая идея. Если вы можете написать void(expr), то вы сможете "инициализировать" анонимный аргумент типа void. Если бы вы также могли написать функцию с произвольным числом void аргументов, был бы способ выполнить несколько выражений в неопределенном порядке, что в некотором роде выражало бы параллелизм.

Что касается обработки списков аргументов разного размера (также известных как вариативные), см. вариативные шаблоны в C++0x, прежде чем вы начнете изучать препроцессор Boost.

person Potatoswatter    schedule 06.09.2010
comment
Спасибо, я этого и боялся. Я думал, что видел что-то в boost.function, где они делали что-то подобное, но, видимо, это не так. И спасибо за совет по вариативным шаблонам, к сожалению, я еще не обновил свои компиляторы (пока). - person Skillcheck; 06.09.2010
comment
Список аргументов функции с одним аргументом типа void преобразуется в список без аргументов. -› на самом деле еще строже. Так и в C99, но в C++ замена чисто синтаксическая: последовательность токенов (void) обозначает список с нулевым параметром. Но, например, (Void), где Void обозначает тип void, недопустим в C++. - person Johannes Schaub - litb; 11.09.2010