如何对推导出的参数类型省略完全转发

How to omit perfect forwarding for deduced parameter type?

本文关键字:类型省 转发 参数      更新时间:2023-10-16

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

#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<T>::type&&代替T&&,但由于相同的结构是避免类型推导的解决方案,在这种情况下它没有帮助。我可以想象一些复杂的技巧来解决这个问题,但代码的清晰度可能会被破坏,它看起来与类似的非模板函数完全不同。

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


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

演示2


在c++14中typename std::enable_if<!std::is_lvalue_reference<T>{}>::type可以缩短为:

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

无论如何,即使在c++11中,如果你发现它更简洁,你也可以使用别名模板来缩短语法:

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

3


使用c++17 constexp -if:

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

使用c++20个概念:

template <typename T>
concept rvalue = !std::is_lvalue_reference_v<T>;
void f(rvalue auto&& v);
void f(const auto& v);

演示4

如何实现第二层:

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

我认为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;
}