【问题标题】:How to omit perfect forwarding for deduced parameter type?如何省略推导参数类型的完美转发?
【发布时间】:2015-11-05 10:23:42
【问题描述】:

假设我有一些函数,一个参数类型(或几个参数类型),我想推导出它的类型。此外,我想要基于事实的不同行为是右值还是左值。由于完美的转发,直接写它会导致一个明显的(对于有经验的人)陷阱:

#include <iostream>
#include <vector>

template <typename T>
void f (T &&v) // thought to be rvalue version
{
   // some behavior based on the fact that v is rvalue
   auto p = std::move (v);
   (void) p;
}

template <typename T>
void f (const T &v) // never called
{  
   auto p = v;
   (void) p;
}

int main ()
{
    std::vector<int> x = {252, 135};
    auto &z = x;
    f (z);
    std::cout << x.size () << '\n'; // woah, unexpected 0 or crash
}

尽管这种行为的鬼鬼祟祟的性质已经是一个有趣的观点,但我的问题实际上是不同的——对于这种情况,什么是好的、简洁、易于理解的解决方法?

如果没有推断出完全转发的类型(例如,它是已知的外部类的模板参数或类似的东西),则使用typename identity&lt;T&gt;::type&amp;&amp; 而不是T&amp;&amp; 的解决方法是众所周知的,但由于相同的构造是避免类型的解决方法扣除在这种情况下没有帮助。我可能可以想象一些 sfinae 技巧来解决它,但代码清晰度可能会被破坏,并且它看起来与类似的非模板函数完全不同。

【问题讨论】:

  • 什么崩溃? size 没有前置条件,因此可以在移动的向量上调用。

标签: c++ c++11 move-semantics perfect-forwarding template-argument-deduction


【解决方案1】:

SFINAE 隐藏在模板参数列表中:

#include <type_traits>

template <typename T
        , typename = typename std::enable_if<!std::is_lvalue_reference<T>{}>::type>
void f(T&& v);

template <typename T>
void f(const T& v);

DEMO


SFINAE 隐藏在返回类型中:

template <typename T>
auto f(T&& v)
    -> typename std::enable_if<!std::is_lvalue_reference<T>{}>::type;

template <typename T>
void f(const T& v);

DEMO 2


typename std::enable_if&lt;!std::is_lvalue_reference&lt;T&gt;{}&gt;::type 可以缩短为:

std::enable_if_t<!std::is_lvalue_reference<T>{}> 

无论如何,即使在 中,如果您觉得更简洁,也可以使用别名模板缩短语法:

template <typename T>
using check_rvalue = typename std::enable_if<!std::is_lvalue_reference<T>{}>::type;

DEMO 3


使用 constexpr-if:

template <typename T>
void f(T&& v)
{
    if constexpr (std::is_lvalue_reference_v<T>) {}
    else {}
}

带有的概念:

template <typename T>
concept rvalue = !std::is_lvalue_reference_v<T>;

void f(rvalue auto&& v);

void f(const auto& v);

DEMO 4

【讨论】:

  • 其实这很好,因为你只需要为右值版本编写它,而左值版本不需要垃圾。唯一的问题可能是人们通常更熟悉移动语义而不是 enable_if 所做的以及为什么在这种情况下应该使用它,但无论如何至少这个解决方案相对较短。
  • @Predelnik 取决于您在函数中实现的行为,也许您可​​以摆脱一次调用std::forward&lt;T&gt;(t) 的单个重载?
  • 是的,我知道通常在这种情况下使用forward 应该是优先考虑的,但是我至少遇到过几次类似的代码,这就是提出问题的原因.
【解决方案2】:

第二级实施怎么样:

#include <utility>
#include <type_traits>

// For when f is called with an rvalue.
template <typename T>
void f_impl(T && t, std::false_type) { /* ... */ }

// For when f is called with an lvalue.
template <typename T>
void f_impl(T & t, std::true_type) { /* ... */ }

template <typename T>
void f(T && t)
{
    f_impl(std::forward<T>(t), std::is_reference<T>());
}

【讨论】:

  • 仅检查 T 是否为引用失败并返回 f&lt;std::vector&lt;int&gt;&amp;&amp;&gt;(std::vector&lt;int&gt;{});
  • 这肯定是有效的解决方案,也没有 sfinae。但是我可以指出以下小问题:如果所讨论的函数恰好是构造函数或运算符,那么命名可能不会像在这种情况下那么明显。同样,不熟悉这种模式的人可能会在第一级函数中添加一些东西,认为它只适用于右值(我猜不太可能,但它仍然可能会引起一些混乱)。您还需要修复@PiotrSkotnicki 提到的小错误。
  • @PiotrSkotnicki:是的,您不想明确指定模板参数。如果这是一个用例,您需要调整代码。
  • @Predelnik:如果您想支持显式模板参数,那么是的,您需要修改代码以仅检查左值引用。 (不过,这是一个奇怪的用例,你应该让参数被推断或转发。)无论如何,你知道该怎么做......
【解决方案3】:

我认为 SFINAE 应该会有所帮助:

template<typename T,
         typename = typename std::enable_if<!std::is_lvalue_reference<T>::value>::type>
void f (T &&v) // thought to be rvalue version
{
   // some behavior based on the fact that v is rvalue
   auto p = std::move (v);
   (void) p;
}

template <typename T>
void f (const T &v) // never called
{  
   auto p = v;
   (void) p;
}

【讨论】:

  • 是的,你是对的。忘了它。更正了答案。
猜你喜欢
  • 1970-01-01
  • 2015-04-22
  • 2021-06-17
  • 1970-01-01
  • 1970-01-01
  • 2017-01-17
  • 2018-11-26
  • 1970-01-01
  • 2016-01-02
相关资源
最近更新 更多