【问题标题】:Using 'void' template arguments in C++在 C++ 中使用“void”模板参数
【发布时间】:2025-12-16 20:20:03
【问题描述】:

举一个最小的例子:

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/c++11/libc++、OS X 10.7 进行了测试。)

我觉得这很奇怪:我本来希望 Type1Type2&lt;void&gt; 表现相同。这里发生了什么?有没有办法重写第二种类型的别名,以便我可以Type2&lt;void&gt; 并得到std::function&lt;void(void)&gt; 而不是错误?

编辑我可能应该补充一点,我想要这样做的原因是允许以下内容:

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

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

Continuation<decltype(someFunc())> c;

Continuation&lt;decltype(someFunc())&gt; 变为 Continuation&lt;void&gt;,我得到了错误。

【问题讨论】:

  • 呵呵,反之亦然(T(void))...
  • 如果我不得不猜测,我会说void(void) 只是void() 的礼貌兼容性表示法,但void 实际上不是有效的参数类型。

标签: c++ templates c++11 std-function


【解决方案1】:

我没有真正的答案,只有我在评论中所说的:你不能将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"; }

【讨论】:

  • 抱歉,不知何故我不小心投了反对票,直到更改它为时已晚才注意到。
【解决方案2】:

简短的回答是“模板不是字符串替换”。 void f(void) 仅在它是 C++ 中 void f() 的别名时才有意义,以便向后兼容 C。

第一步是使用可变参数,如其他地方所述。

第二步是弄清楚如何将void 返回函数映射到...好吧,可能是std::function&lt;void()&gt; 之类的东西,或者可能是其他东西。我说的可能是别的,因为与其他情况不同,你不能调用std::function&lt;void()&gt; foo; foo( []()-&gt;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;

它为您提供所需的类型。你甚至可以添加一个 apply 到 continuation:

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

如果传入的类型是 void 或非 void 类型,它可以让您对函数的执行统一应用延续。

但是,我会问“你为什么要这样做”?

【讨论】:

  • 谢谢,这为我指明了正确的方向。 为什么我想这样做?我正在尝试从 Microsoft 的Task Parallel Library 实现一个简单版本的 Futures。延续接收它所依赖的任务的返回值作为参数。
【解决方案3】:

几个答案已经解释了基本原理。为了补充这些答案,规范说 (C++11 §8.3.5[dcl.func]/4):

由一个非依赖类型void 的未命名参数组成的参数列表是 相当于一个空的参数列表。除了这种特殊情况,参数的类型不得为 cv void

在您的Type2 示例中,void(T) 中的T 是一个依赖类型——它依赖于模板参数。

【讨论】:

  • 这也意味着像void(typename std::type_identity&lt;void&gt;::type)void(std::void_t&lt;T&gt;)这样的非依赖类型也可以工作,这很奇怪
【解决方案4】:

当一个函数被声明为采用void 类型的参数时,就像在std::function&lt;void(void)&gt; 中一样,这实际上只是一种愚蠢的说法,即它采用零参数。但是您声明 Type2 的方式是 std::function,其签名不返回任何内容 (void),但需要 1 个参数。 void 不是可以用作参数的类型,它只是一种声明没有参数的方式。所以它不适用于 Type2,因为这需要一个可以用作参数的实际类型。

【讨论】:

    【解决方案5】:

    如果将 Void 传递给函数,则可以将其解释为空参数。毕竟你没有使用空指针

    void func (void)
    

    变成

    void func ()
    

    【讨论】: