如何在C++中编程确定一个表达式是右值还是左值?

54

在C++中,确定表达式是右值还是左值的最佳方法是什么?也许在实践中这并不有用,但因为我正在学习右值和左值,所以我认为编写一个名为 is_lvalue 的函数会很好,如果传入的表达式是左值,则返回true,否则返回false。

示例:

std::string a("Hello");
is_lvalue(std::string()); // false
is_lvalue(a); // true  
4个回答

69
大部分工作已经被标准库完成,您只需要一个函数包装器:
template <typename T>
constexpr bool is_lvalue(T&&) {
  return std::is_lvalue_reference<T>{};
}

如果传递一个std::string左值,则T将推断为std::string&const std::string&,对于右值,它将推断为std::string。请注意,Yakk的答案将返回不同类型,这允许更多的灵活性,您应该阅读该答案并可能使用它。

30

我使用了两个重载的模板函数解决了上述问题。第一个函数以左值的引用作为输入,并返回true。而第二个函数则使用右值引用。然后我让编译器根据输入的表达式来匹配正确的函数。

代码:

#include <iostream>

template <typename T>
constexpr bool is_lvalue(T&) {
    return true;
}

template <typename T>
constexpr bool is_lvalue(T&&) {
    return false;
}

int main()
{
    std::string a = std::string("Hello");
    std::cout << "Is lValue ? " << '\n';
    std::cout << "std::string() : " << is_lvalue(std::string()) << '\n';
    std::cout << "a : " << is_lvalue(a) << '\n';
    std::cout << "a+b : " << is_lvalue(a+ std::string(" world!!! ")) << '\n';
} 

输出:

Is Lvalue ? 
std::string() : 0
a : 1
a+b : 0

8
由于这些参数未被使用,您应该将这两个函数标记为constexpr,因为类型在编译时始终已知。 - Ryan Haining
5
жИСдЉЪињФеЫЮstd::true_typeеТМfalse_typeдї•иОЈеЊЧжЫійЂШзЪДз°ЃеЃЪжАІгАВ - Yakk - Adam Nevraumont
2
@Yakk 在这种情况下使用 std::true_type 的优点是什么? - Giuseppe Pes
3
它将结果的编译时特性移入类型系统中,这样您就可以像将其传递给模板函数一样进行操作,并确保传播其值。同时,真正的类型具有constexpr operator bool,因此它也可以在该上下文中使用。 - Yakk - Adam Nevraumont
1
从技术上讲,你实际上是为左值引用和转发引用创建了一个重载,而编译器只是选择更好的匹配。 - Krystian S
显示剩余2条评论

21

我会借鉴boost::hana的做法,使is_lvalue的返回值将其参数的左值性编码为constexpr值和类型。

这使您可以进行标签分派而无需额外的模板代码。

template<class T>
constexpr std::is_lvalue_reference<T&&>
is_lvalue(T&&){return {};}

这个函数的主体并没有做任何事情,参数值也被忽略了。这使得即使在非常数表达式的情况下,它仍可以是constexpr。

使用这种技术的一个优点可以在这里看到:

void tag_dispatch( std::true_type ) {
  std::cout << "true_type!\n";
}
void tag_dispatch( std::false_type ) {
  std::cout << "not true, not true, shame on you\n";
}

tag_dispatch( is_lvalue( 3 ) );

is_lvalue 的返回值不仅在 constexpr 上下文中可用(因为 true_typefalse_type 有一个 constexpr operator bool),而且我们可以根据其状态轻松选择重载函数。

另一个优点是这使得编译器很难不内联结果。使用 constexpr 值,编译器可能会“轻松”忘记它是一个真正的常量;但对于类型,必须首先将其转换为 bool 才能有可能遗忘。


为什么要使用<T&&>而不是<T> - Ryan Haining
2
@RyanHaining 更好的错误信息?对我来说,在重载解析失败时,is_lvalue_reference<Foo&&> 似乎比 is_lvalue_reference<Foo> 更清晰。 - Yakk - Adam Nevraumont
1
@Barry 这些答案已经足够好了,所以我提出了一个改进意见。但是没有人采纳并继续完善它,所以我发布了我的改进版本,并解释了为什么它略微更好。 - Yakk - Adam Nevraumont
4
@Yakk 这是 C++。我们都在追求略微更好的东西。 - Barry

8
使用 std::is_lvalue_referencestd::is_rvalue_reference
如果您愿意使用 decltype,那么您不需要一个包装器。
std::string a("Hello");
std::is_lvalue_reference<decltype((std::string()))>::value; // false
std::is_lvalue_reference<decltype((a))>::value; // true

在C++17中,您将能够使用以下内容:

std::string a("Hello");
std::is_lvalue_reference_v<decltype((std::string()))>; // false
std::is_lvalue_reference_v<decltype((a))>; // true

或者你可以像@Ryan Haining建议的那样编写一个包装器,只需确保正确获取类型即可。


在C++17中,您将能够做的事情在C++14甚至C++11中都可以做到。C++17只是已经将它们与stdlib一起发布了。 - edmz
@black 将“you'll be able to”解读为“你将能够(不需要额外的努力)”。 - Pharap

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接