【问题标题】:Is there a way to emplace return value?有没有办法设置返回值?
【发布时间】:2013-08-23 14:36:36
【问题描述】:
// VERSION 1
struct Range { int begin, end; };
inline Range getRange()
{
    int newBegin, newEnd;
    // do calculations
    return {newBegin, newEnd};
}
struct Test
{
    std::vector<Range> ranges;
    inline void intensive()
    {
        ranges.push_back(getRange());
        // or ranges.emplace_back(getRange());
        // (gives same performance results)
    }
};

// VERSION 2
struct Range { int begin, end; };
struct Test
{
    std::vector<Range> ranges;
    inline void intensive()
    {
       int newBegin, newEnd;
       // do calculations
       ranges.emplace_back(newBegin, newEnd);
    }
};

版本 2 总是比 版本 1 快。

事实上,getRange() 被多个类使用。如果我要应用 version 2,会有很多代码重复。

另外,我不能将ranges 作为对getRange() 的非常量引用传递,因为其他一些类使用std::stack 而不是std::vector。我将不得不创建多个重载并有更多的代码重复。

有没有一种通用的方式/习惯来替换返回值

【问题讨论】:

  • "版本 2 总是比版本 1 快。" -- 这个断言是基于……什么?
  • @MohammadAliBaydoun:不,句号,回头再想一想。
  • @user1837009 没有。返回一个 initializer_list 在道德上等同于返回一个引用。 (这也无济于事)
  • @Vittorio 好吧,如果你要像这样重载,你真的不需要所有这些东西,你可以只拥有两个重载函数而不需要额外的模板类(我假设 TTItemArgs 在你的真实代码中是已知的并且是固定的,所以你只剩下TArgs)。我想到的是something along those lines 允许支持emplaceemplace_backany 容器,例如。 queue/list/deque 如果您将来碰巧需要它们。
  • @VittorioRomeo 我正在尝试将一个示例放在一起,但我遇到了麻烦,因为emplaceemplace_back 已超载,因此它默默地未能通过 SFINAE 测试并且从未检测到该功能。请给我一点时间... ;)

标签: c++ c++11 templates return emplace


【解决方案1】:

根据我们在 cmets 中关于使用 SFINAE 允许在任何类型的容器(无论它支持 emplace 还是 emplace_back)上的讨论,这里是一个示例实现。

您只需要一种方法来检测emplaceemplace_back 是否可用,并相应地调度调用。为此,我们将使用 SFINAE:

namespace detail
{
    template<typename T, typename... Args>
    auto emplace_impl(int, T& c, Args&&... pp)
        -> decltype(c.emplace_back(std::forward<Args>(pp)...))
    {
        return c.emplace_back(std::forward<Args>(pp)...);
    }

    template<typename T, typename... Args>
    auto emplace_impl(long, T& c, Args&&... pp)
        -> decltype(c.emplace(std::forward<Args>(pp)...))
    {
        return c.emplace(std::forward<Args>(pp)...);
    }
} // namespace detail

template<typename T, typename... Args>
auto emplace(T& c, Args&&... pp)
    -> decltype(detail::emplace_impl(0, c, std::forward<Args>(pp)...))
{
    return detail::emplace_impl(0, c, std::forward<Args>(pp)...);
}

感谢@DyP,他提供了这个much更好更短的 C++11 解决方案(参见 cmets)。以前基于特征的解决方案(修订版 3 和 4)要冗长得多。


使用起来非常简单:

template<typename Container>
void test_emplace()
{
  Container c;
  emplace(c, 3);
}

int main()
{
  test_emplace<std::queue<int>>();
  test_emplace<std::stack<int>>();
  test_emplace<std::deque<int>>();
  test_emplace<std::list<int>>();
  test_emplace<std::vector<int>>();
}

我会让您在我的 test_emplace() 用法示例和您的实际代码之间架起一座桥梁,但现在应该不会太难。 ;)

【讨论】:

  • 检查我的回答here,了解如何正确检查是否可以在 C++11 中调用具有某些参数的函数。
  • “因为标准容器要求它们的项目是CopyConstructible”......不,不是。 std::vector&lt;std::unique_ptr&lt;int&gt;&gt; 很好。
  • 不要测试是否存在具有特定签名的成员。测试表达式的有效性(即使用decltype(std::declval&lt;T&gt;().emplace(std::declval&lt;whatever&gt;()))
  • 不,忘记复制/移动任何可构造的东西。如果你把它设为HasEmplace&lt;Container, Args...&gt;::value,你可以直接测试确切的参数:)
  • std::declval&lt;Args&gt;()... 有帮助吗? (它会扩展为类似std::declval&lt;Arg0&gt;(), std::declval&lt;Arg1&gt;(), std::declval&lt;Arg2&gt;())。
【解决方案2】:

这是一种方法,您可以将代码传递到GetRange,而无需知道您要放入的是什么:

template<typename Emplacer>
void GetRange( Emplacer emplace ) {
  int beg, end;
  // ...
  emplace( beg, end );
}

std::vector<Range> ranges;
inline void intensive()
{
   GetRange( [&]( int b, int e ) {
     ranges.emplace_back( b, e );
   } );
}

【讨论】:

  • 不错,+1。虽然在每个呼叫站点使用 lambda 可能有点麻烦,但我仍然喜欢这种设计。
【解决方案3】:

不,正在使用getRange() 进行构造,而emplace_backvector 中完成构造。

【讨论】:

  • 我明白了。我希望编译器能够(或将会)自己进行这些优化。我只能考虑为std::vectorstd::stack 安置创建一个通用接口并将容器作为非常量引用传递,但我想稍微重复代码会更好。
  • This is what I got so far,它可以工作,但我相信它会更好。关于如何改进它的任何想法?
猜你喜欢
  • 2021-01-29
  • 1970-01-01
  • 2011-12-11
  • 2023-02-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-02
相关资源
最近更新 更多