如何省略推导参数类型的完美转发?
How to omit perfect forwarding for deduced parameter type?
假设我有一些函数,我想推导其类型的参数类型(或几种参数类型)。我也想要基于事实的不同行为是右值还是左值。由于完美转发,直接写它会导致一个明显的(对于有经验的人)陷阱:
#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
}
尽管这种行为的偷偷摸摸的本质已经很有趣了,但我的问题实际上是不同的——对于这种情况,什么是好的、简洁的、可以理解的解决方法?
如果没有推导出完美转发的类型(例如,它是已知的外部 class 模板参数或类似的东西),有一个众所周知的解决方法,使用 typename identity<T>::type&&
而不是 T&&
但是由于相同的构造是避免类型推导的变通方法,因此在这种情况下无济于事。我可能会想出一些 sfinae 技巧来解决它,但代码清晰度可能会被破坏,它看起来与类似的非模板函数完全不同。
二级实施如何:
#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;
}
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 隐藏在 return 类型中:
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);
在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;
使用 c++17 constexpr-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);
假设我有一些函数,我想推导其类型的参数类型(或几种参数类型)。我也想要基于事实的不同行为是右值还是左值。由于完美转发,直接写它会导致一个明显的(对于有经验的人)陷阱:
#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
}
尽管这种行为的偷偷摸摸的本质已经很有趣了,但我的问题实际上是不同的——对于这种情况,什么是好的、简洁的、可以理解的解决方法?
如果没有推导出完美转发的类型(例如,它是已知的外部 class 模板参数或类似的东西),有一个众所周知的解决方法,使用 typename identity<T>::type&&
而不是 T&&
但是由于相同的构造是避免类型推导的变通方法,因此在这种情况下无济于事。我可能会想出一些 sfinae 技巧来解决它,但代码清晰度可能会被破坏,它看起来与类似的非模板函数完全不同。
二级实施如何:
#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;
}
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 隐藏在 return 类型中:
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);
在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;
使用 c++17 constexpr-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);