Использование аргументов шаблона 'void' в C++

Возьмем следующий минимальный пример:

using Type1 = std::function<void(void)>;

template <typename T>
using Type2 = std::function<void(T)>;

Type1 whyDoesThisWork;
Type2<void> andYetThisDoesNot;

Если псевдоним второго типа, я получаю сообщение об ошибке «Аргумент может не иметь типа void». (Я тестировал Xcode 4.5, Clang/С++ 11/libС++, OS X 10.7.)

Я нахожу это любопытным: я ожидал, что Type1 и Type2<void> будут вести себя одинаково. Что тут происходит? И есть ли способ переписать псевдоним второго типа, чтобы я мог написать Type2<void> и получить std::function<void(void)> вместо ошибки?

Изменить Вероятно, я должен добавить, что причина, по которой я хочу этого, заключается в том, чтобы разрешить что-то вроде следующего:

template <typename ... T>
using Continuation = std::function<void(T...)>;

auto someFunc = []() -> void {
  printf("I'm returning void!\n");
};

Continuation<decltype(someFunc())> c;

Continuation<decltype(someFunc())> становится Continuation<void>, и я получаю сообщение об ошибке.


person Nick Hutchinson    schedule 14.11.2012    source источник
comment
Хе-хе, это работает наоборот (T(void))...   -  person Kerrek SB    schedule 14.11.2012
comment
Если бы мне пришлось угадывать, я бы сказал, что void(void) — это просто условное обозначение совместимости для void(), но void на самом деле не является допустимым типом аргумента.   -  person Kerrek SB    schedule 14.11.2012


Ответы (5)


Короткий ответ: «шаблоны не являются заменой строк». void f(void) имеет значение только в том случае, если это псевдоним для void f() в C++, чтобы обеспечить обратную совместимость с C.

Первым шагом является использование вариативных переменных, как отмечалось в другом месте.

Второй шаг — выяснить, как сопоставить void возвращающие функции с... ну, может быть, что-то вроде std::function<void()>, а может, что-то еще. Я говорю, может быть, что-то еще, потому что, в отличие от других случаев, вы не можете вызвать std::function<void()> foo; foo( []()->void {} ); -- это не настоящее продолжение.

Что-то вроде этого, может быть:

template<typename T>
struct Continuation
{
  typedef std::function<void(T)> type;
};

template<>
struct Continuation<void>
{
  typedef std::function<void()> type;
};

затем используйте его следующим образом:

auto someFunc = []()->void {};
Continuation<decltype(someFunc())>::type c;

который дает вам тип, который вы хотите. Вы даже можете добавить применение к продолжению:

template<typename T>
struct Continuation
{
  typedef std::function<void(T)> type;

  template<typename func, typename... Args>
  static void Apply( type const& cont, func&& f, Args... args)
  {
    cont( f(args...) );
  }
};

template<>
struct Continuation<void>
{
  typedef std::function<void()> type;
  template<typename func, typename... Args>
  static void Apply( type const& cont, func&& f, Args... args)
  {
    f(args...);
    cont();
  }
};

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

Однако я бы спросил: «Зачем вам это делать»?

person Yakk - Adam Nevraumont    schedule 14.11.2012
comment
Спасибо, это указало мне правильное направление. Зачем мне это делать? Я пытаюсь реализовать простую версию Futures из библиотеки параллельных задач Microsoft. Продолжение получает в качестве аргументов возвращаемые значения задач, от которых оно зависит. - person Nick Hutchinson; 15.11.2012

У меня нет фактического ответа, только то, что я сказал в комментарии: у вас не может быть void в качестве типа функции, как в:

int foo(int, char, void, bool, void, void);     // nonsense!

Я считаю, что T(void) разрешено только в качестве нотации совместимости для C (которая различает декларации и прототипы, что сильно отличается от C++, и которая должна иметь возможность сказать «без аргументов ").

Итак, решение должно быть вариативным:

template <typename ...Args> using myType = std::function<void(Args...)>;

Таким образом, вы можете правильно иметь без аргументов:

myType<> f = []() { std::cout << "Boo\n"; }
person Kerrek SB    schedule 14.11.2012
comment
Извините, мне как-то удалось случайно проголосовать за это и не заметить, пока не стало слишком поздно, чтобы изменить это. - person Gerald; 07.11.2013

Несколько ответов уже объясняют обоснование. Чтобы добавить к этим ответам, в спецификации говорится (С++ 11 §8.3.5 [dcl.func]/4):

Список параметров, состоящий из одного безымянного параметра независимого типа void, эквивалентен пустому списку параметров. За исключением этого особого случая, параметр не должен иметь тип cv void.

В вашем примере Type2 T в void(T) является зависимым типом — он зависит от параметра шаблона.

person James McNellis    schedule 14.11.2012
comment
Это также означает, что независимые типы, такие как void(typename std::type_identity<void>::type) и void(std::void_t<T>), также работают, что странно. - person Artyer; 18.08.2020

Когда объявлена ​​функция, принимающая параметр типа void, как в std::function<void(void)>, на самом деле это просто глупый способ сказать, что она принимает нулевые параметры. Но то, как вы объявили Type2, - это std::function с подписью, которая ничего не возвращает (void), но принимает 1 параметр. void — это не тип, который можно использовать в качестве параметра, это просто способ объявить, что параметров нет. Так что это не работает с Type2, потому что для этого требуется фактический тип, который можно использовать в качестве параметра.

person Benjamin Lindley    schedule 14.11.2012

Void можно интерпретировать как пустой параметр, если вы передаете его функции. В конце концов, вы не используете указатель void, поэтому

void func (void)

становится

void func ()
person count0    schedule 14.11.2012