`decltype(auto)` 变量是否有任何现实的用例?

Are there any realistic use cases for `decltype(auto)` variables?

根据我的个人经验和咨询对 What are some uses of decltype(auto)? 等问题的回答,我可以找到很多 decltype(auto) 作为 函数 return 类型的有价值的用例占位符.

但是,我很难为 decltype(auto) 变量想出任何有效的(即有用的、现实的、有价值的)用例。想到的唯一可能性是存储函数 return ing decltype(auto) 的结果以供以后传播,但是 auto&& 也可以在那里使用并且会更简单。

我什至搜索了我所有的项目和实验,出现的 391 次 decltype(auto) 都是 return 类型的占位符。

那么,decltype(auto) 变量是否有实际用例? 或者此功能仅在用作 return 类型占位符时有用?


How do you define "realistic"?

我正在寻找一个提供价值的用例 (即它不仅仅是一个展示该功能如何工作的示例) 其中 decltype(auto) 是完美的选择,与 auto&& 之类的替代方案相比,或者根本不声明变量。

问题域无关紧要,它可能是一些晦涩的元编程极端情况或神秘的函数式编程构造。然而,这个例子需要让我去 "Hey, that's clever/beautiful!" 并且使用任何其他功能来实现相同的效果将需要更多的样板或有某种缺点。

本质上,变量的大小写与函数相同。这个想法是我们用 decltype(auto) 变量存储函数调用的结果:

decltype(auto) result = /* function invocation */;

那么,result就是

  • 如果结果是纯右值,则为非引用类型,

  • a(可能是 cv 限定的)左值引用类型,如果结果是左值,或

  • 一个右值引用类型,如果结果是一个 xvalue。

现在我们需要一个新版本的 forward 来区分 prvalue 情况和 xvalue 情况:(避免使用名称 forward 以防止 ADL 问题)

template <typename T>
T my_forward(std::remove_reference_t<T>& arg)
{
    return std::forward<T>(arg);
}

然后使用

my_forward<decltype(result)>(result)

不同于std::forward,这个函数是用来转发decltype(auto)变量的。因此,它并不是无条件地return一个引用类型,应该用decltype(variable)来调用,可以是TT&T&& , 这样它就可以区分左值、x 值和纯右值。因此,如果 result

  • 一个非引用类型,然后用一个非引用调用第二个重载 T,一个非引用类型被 returned,结果是纯右值;

  • 一个左值引用类型,然后使用 T& 调用第一个重载,并且 T& 被 returned,产生一个左值;

  • 一个右值引用类型,然后使用 T&& 调用第二个重载,并且 T&& 被 returned,产生一个 xvalue。

举个例子。假设您想包装 std::invoke 并在日志中打印一些内容:(示例仅供参考)

template <typename F, typename... Args>
decltype(auto) my_invoke(F&& f, Args&&... args)
{
    decltype(auto) result = std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
    my_log("invoke", result); // for illustration only
    return my_forward<decltype(result)>(result);
}

现在,如果调用表达式是

  • 一个纯右值,则result是非引用类型,函数return是非引用类型;

  • 一个非常量左值,那么result是一个非常量左值引用,函数return是一个非常量左值引用类型;

  • 一个const左值,那么result是一个const左值引用,函数return是一个const左值引用类型;

  • 一个xvalue,那么result是一个右值引用类型,函数return是一个右值引用类型。

给定以下功能:

int f();
int& g();
const int& h();
int&& i();

以下断言成立:

static_assert(std::is_same_v<decltype(my_invoke(f)), int>);
static_assert(std::is_same_v<decltype(my_invoke(g)), int&>);
static_assert(std::is_same_v<decltype(my_invoke(h)), const int&>);
static_assert(std::is_same_v<decltype(my_invoke(i)), int&&>);

(live demo, move only test case)

如果改用 auto&&,代码在区分纯右值和 x 值时会遇到一些问题。

可能不是很深的答案,但基本上decltype(auto) was proposed用于return类型推导,能够在return类型时推导引用实际上是一个引用(与永远不会推断出引用的普通 auto 或总是这样做的 auto&& 相反)。

它也可以用于变量声明这一事实并不一定意味着应该有优于其他的场景。事实上,在变量声明中使用 decltype(auto) 只会使代码阅读复杂化,因为对于变量声明,它具有完全相同的含义。另一方面,auto&& 形式允许您声明一个常量变量,而 decltype(auto) 则不允许。