【问题标题】:Create a "do-nothing" `std::function` with any signature?创建一个带有任何签名的“无所事事”`std::function`?
【发布时间】:2015-07-07 16:56:38
【问题描述】:

我想创建一个带有任意签名的简单无操作 std::function 对象。为此,我创建了两个函数:

template <typename RESULT, typename... ArgsProto>
  std::function<RESULT(ArgsProto...)> GetFuncNoOp()
  {
    // The "default-initialize-and-return" lambda
    return [](ArgsProto...)->RESULT { return {}; };
  }

template <typename... ArgsProto>
  std::function<void(ArgsProto...)> GetFuncNoOp()
  {
    // The "do-nothing" lambda
    return [](ArgsProto...)->void {};
  }

这些都足够好(显然第一个版本可能会在RESULT 对象中创建未初始化的数据成员,但实际上我认为这不会有太大问题)。但是第二个,void-returning 版本是必要的,因为return {}; never 返回void(这将是一个编译错误),它不能写成模板专业化第一个是因为初始签名是可变的。

所以我不得不在这两个函数之间进行选择,只实现一个,或者给它们不同的名字。但我真正想要的只是轻松地初始化std::function 对象,这样,当被调用时,它们除了抛出异常之外什么都不做。这可能吗?

注意the default constructor of std::function does not do what I want

【问题讨论】:

  • std::function&lt; std::string const&amp;() &gt; 是该技术失败的一个例子(实际上,不能失败)。
  • @Yakk 对,因为(当然)“无操作”函数不可能返回引用,因为它可能返回对什么的引用?
  • static T; return T; ;)
  • @Yakk 哦,对,当然。可以通过返回 static anything instance{} 之类的内容将其应用于您的解决方案吗?
  • @MSalters 而不是 =delete 在编译时捕获错误。一个总是抛出的函数似乎是一个不好的函数来存储在某个地方!

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


【解决方案1】:

你太拘泥于牙套了。

template <typename RESULT, typename... ArgsProto>
std::function<RESULT(ArgsProto...)> GetFuncNoOp()
{
  // && avoids unnecessary copying. Thanks @Yakk
  return [](ArgsProto&&...) { return RESULT(); }; 
}

【讨论】:

  • 哦,我没有意识到void() 可以以这种方式“初始化”。那里到底发生了什么?和return;有什么不同吗?
  • @KyleStrand 没什么不同。 void()void 类型的纯右值,仅此而已。
  • ....我注意到 RESULT{} 的行为不一样。 “通用”初始化语法的另一个问题?
  • @KyleStrand 是的。 void() 很好; void{} 不是。不知道他们为什么这样做。
【解决方案2】:

我不喜欢指定签名。

假设您有一个std::function 实现,其中std::function&lt;void()&gt; 可以接受int(*)() 类型的函数指针,这是一个非类型擦除noop 对象,可以转换为任何std::function

struct noop {
  struct anything {
    template<class T>
    operator T(){ return {}; }
    // optional reference support.  Somewhat evil.
    template<class T>
    operator T&()const{ static T t{}; return t; }
  };
  template<class...Args>
  anything operator()(Args&&...)const{return {};}
};

如果您的std::function 不支持该转换,我们添加:

  template<class...Args>
  operator std::function<void(Args...)>() {
    return [](auto&&...){};
  }

假设您的 std::function 对 SFINAE 友好,那么它应该可以处理这种情况。

live example.

要使用,只需使用noop{}。如果你真的需要一个返回 noop 的函数,请执行inline noop GetFuncNoop(){ return{}; }

这样做的一个附带好处是,如果您将 noop 传递给非类型擦除操作,我们不会因为什么都不做而获得毫无意义的 std::function 开销。

引用支持是邪恶的,因为它创建了一个全局对象并在各处传播对它的引用。如果调用了一个std::function&lt;std::string&amp;()&gt;,并且修改了生成的string,则修改后的字符串将在任何地方使用(并且在使用之间没有任何同步)。再加上不告诉任何人就分配全球资源似乎很粗鲁。

我只需要 =delete operator T&amp; 的情况,并生成编译时错误。

【讨论】:

  • 嗯。指定签名确实是 T.C. 对我的解决方案更正的一个非常烦人的问题,所以我更喜欢这个,但我不太明白。为什么anything::()&lt;void&gt; {return {}; }(即anything::()void的特化)不会导致编译错误,因为return {};不能用于返回void
  • 另外,您是否介意将reference-returning 版本放回您的答案中,并简要说明您为什么认为它是“邪恶的”(或危险的或其他)?
  • @KyleStrand std::function&lt;void()&gt; 在现代实现中从不尝试将传入对象的返回值转换为void。所以返回的anything 就被丢弃了。现在添加了返回引用的anything 的一些邪恶,我使它工作(原始版本不起作用,需要对const 进行一些技巧才能使其正常工作)。 noop 总是 返回anythinganything 可以转换为任何东西,这使它可以匹配任何std::function 签名的返回值要求。
  • 太棒了。我将operator T&amp;() 更改为返回T const&amp;(并将static T t 更改为static const T t),这提供了一些 安全性,并且对于无操作功能来说,这可能已经足够好了。
  • ....anything::() 重载如何消除歧义? noop::() 永远不会返回 const,但一切似乎都按预期工作(尽管我预计实际的打印操作是未定义的行为):coliru.stacked-crooked.com/a/5831256cb7e60d22
【解决方案3】:
template <class T> struct dummy {
  static auto get() -> T { return {}; }
};
template <> struct dummy<void> {
  static auto get() -> void {}
};

template <typename RESULT, typename... ArgsProto>
std::function<RESULT(ArgsProto...)> GetFuncNoOp()
{
  return [](ArgsProto...)->RESULT { return dummy<RESULT>::get(); };
}

但是...@T.C.解决方案更加优雅。只是想展示另一种方法,它可以应用于任何你需要专门化某事物的“一部分”的地方。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-03-17
    • 1970-01-01
    • 1970-01-01
    • 2014-11-27
    • 1970-01-01
    • 2021-07-13
    • 1970-01-01
    相关资源
    最近更新 更多