你能在C++23中完美地转发表达式吗?

6ss1mwsb  于 2023-07-01  发布在  其他
关注(0)|答案(3)|浏览(83)

在C++23中,由于auto()auto{},完美转发纯右值变得更容易。有了这个新工具,现在是否可以为表达式e形成FORWARD(e)表达式,并满足以下要求?

  1. FORWARD(e)e具有相同的类型,忽略引用限定符
    1.如果decltype(e)是左值/右值引用,则FORWARD(e)分别是左值/x值
    1.否则,FORWARD(e)e具有相同的值类别
    1.不可能发生额外的复制或移动
    我们已经可以用std::forward做不完美转发了:
#define FORWARD(...) ::std::forward<decltype(__VA_ARGS__)>(__VA_ARGS__)

这将保留类型并适当地转换引用。但是,纯右值将被转换为xvalue,因为std::forward返回一个右值引用(即FORWARD(e)是转换后的x值)。
因此:

T x = T();          // copy elision because x is initialized to prvalue
T x = FORWARD(T()); // calls move constructor

在C++中是否可以实现 true 完美转发,包括保留纯右值?

biswetbf

biswetbf1#

你基本上希望FORWARD(e)e,除非e碰巧命名了一个右值引用变量,在这种情况下,你希望是move(e)
您可以简单地转换为decltype((e))类型(如果e是纯右值,则将被省略),除非e是右值引用变量,因为decltype((e))将是左值引用。

#include <type_traits>

template<typename IdType, typename ExprType>
using perfect_forward_cast = std::conditional_t<std::is_rvalue_reference_v<IdType>,
    IdType,
    ExprType
>;

#define FORWARD(...) ::perfect_forward_cast<decltype( __VA_ARGS__ ), decltype(( __VA_ARGS__ ))>( __VA_ARGS__ )

https://godbolt.org/z/WYehMxzPb

cxfofazt

cxfofazt2#

没有
给定E,我们现在可以做auto(E),但它总是一个纯右值。左值和x值被具体化为纯右值。
我们也可以做static_cast<decltype(E)&&>(E) †,但这始终是一个glvalue。纯右值被具体化为xvalue。
P0849,这个给我们带来auto(E)的提案,最初也提出了decltype(auto)(E)。这样的语法意味着:

  • 如果E可以是纯右值,那么decltype(auto)(E)将“完美转发”E,但这是没有意义的,因为这里E已经在做正确的事情。
  • 如果E不能是纯右值(如,E是转发引用的函数参数的名称),则decltype(auto)(E)的含义与static_cast<decltype(E)&&>(E)相同。

后者将提供一种转发方式,而无需宏FWD(arg)或人们倾向于编写的C风格(Arg&&) arg转换。然而,decltype(auto)(name)比这两种方法都更长,也更神秘,因此它被从提案中删除。
一旦你得到了一个函数参数,你就不再有纯右值了--没有办法推导出xvalue和纯右值之间的区别--所以我想不出有什么真实的的用途来完善转发,它也处理纯右值,因为你可以直接使用表达式。
†实现转发的正确方法是:

#define FORWARD(e) static_cast<decltype(e)&&>(e)

#define FORWARD(e) std::forward<decltype(e)>(e)

这两种方法的意思是一样的,但是前者避免了示例化std::forward和一些内部类型特征,所以编译起来更快。

yqyhoc1h

yqyhoc1h3#

是的,这是可能的,它不需要C++23。如果我们可以在宏中检测到表达式的值类别,我们可以选择从IILE返回此表达式。这将受到强制复制初始化的影响,并保留纯右值类别:

namespace detail {

enum class value_category {
    lvalue,
    xvalue,
    prvalue
};

template <typename T>
constexpr auto category_of(std::remove_reference_t<T>& t)
    -> std::integral_constant<value_category, value_category::lvalue>;

template <typename T>
constexpr auto category_of(std::remove_reference_t<T>&& t)
    -> std::integral_constant<value_category, (std::is_reference_v<T> ? value_category::xvalue : value_category::prvalue)>;

} // namespace detail

这些辅助函数与std::forward非常相似,它们可以接受左值、x值和纯右值。我们不返回xvalues和prvalues的右值引用,而是使用它们来检测值类别是什么。
现在我们有了一种检测它的方法,我们可以定义实际的FORWARD宏:

#define FORWARD(...) [&]() -> decltype(auto) { \
    constexpr auto c = decltype(::detail::category_of<decltype(__VA_ARGS__)>(__VA_ARGS__))::value; \
    if constexpr (c == ::detail::value_category::prvalue) { \
        return (__VA_ARGS__); \
    } \
    else { \
        return static_cast<decltype(__VA_ARGS__)&&>(__VA_ARGS__); \
    } \
}()

多亏了if constexpr和推导出的decltype(auto)返回类型,我们的IILE有时可以返回引用,有时可以返回值,这取决于检测到的类别。
这可以完美地保留类型和值类别。请参阅Compiler Explorer以获得现场演示。

相关问题