右值或左值(const)引用形参

rvalue or lvalue (const) reference parameter

本文关键字:引用 形参 const      更新时间:2023-10-16

我想通过r值或l值(const)引用将参数(某些具体类型,例如int)传递给成员函数。我的解决方案是:

#include <type_traits>
#include <utility>
struct F
{
    using desired_parameter_type = int;
    template< typename X, typename = typename std::enable_if< std::is_same< typename std::decay< X >::type, desired_parameter_type >::value >::type >
    void operator () (X && x) const
    {
        // or even static_assert(std::is_same< typename std::decay< X >::type, desired_parameter_type >::value, "");
        std::forward< X >(x); // something useful
    }
};

另一个例子是http://pastebin.com/9kgHmsVC.

但是太啰嗦了。如何用更简单的方法来做呢?

也许我应该用std::remove_referencestd::remove_const的叠加而不是std::decay,但这里只是一个简化。

如果我正确理解您的问题,您希望有一个函数,其参数是右值引用(在提供右值的情况下)或左值引用const(在提供左值的情况下)。

但是这个函数能做什么呢?好吧,因为它必须能够处理这两种情况,包括提供左值的情况,它不能修改它的输入(至少不能修改绑定到x参数的输入)——如果它这样做了,它将违反const引用的语义。

但是,如果不能改变形参的状态,就没有理由允许右值引用:让x始终是对const的左值引用。对const的左值引用可以绑定到右值,所以你可以同时传递左值和右值。

如果函数的语义根据传递的内容不同而不同,那么我会说写两个这样的函数更有意义:一个接受右值引用,一个接受左值引用到const

正如Andy所提到的,这里重要的是你可以在函数中做什么才有意义。

  • 可以将参数转发给另一个函数。在这种情况下,使用模板并不重要,因为如果给出了错误的形参类型,它仍然会产生编译错误(将错误的类型发送给第二个函数)。您可以使用template <typename T> blah (T && x)
  • 其他任何东西都需要你根据它是r值引用还是l值引用来编写不同的代码,所以你无论如何都需要编写2个函数:blah (const int & x)blah (int && x)

我假设您一定在尝试第一个选项,并且您正在尝试使任何潜在的编译器错误更加用户友好。好吧,我会说这不值得;程序员仍然会在任何像样的编译器的输出中看到一个"called by…"列表。

实际上,这是一个非常好的问题。到目前为止,我也一直在使用通用参考技巧加上enable_if锤子。这里我给出了一个不使用模板的解决方案,它使用左值强制转换作为替代方案。

下面是一个真实的例子,其中使用已知的就地使用ofstream的例子,这在c++ 98中是不可能的(或很难)(我在示例中使用ostringstream以使其更清楚)。

首先,您将看到一个左值引用函数,就像c++ 98中经常看到的那样。

#include<iostream>
#include<sstream>
struct A{int impl_;};
std::ostringstream& operator<<(std::ostringstream& oss, A const& a){
    oss << "A(" << a.impl_ << ")"; // possibly much longer code.
    return oss;
}
// naive C++11 rvalue overload without using templates
std::ostringstream& operator<<(std::ostringstream&& oss, A const& a){
    oss << "A(" << a.impl_ << ")"; // ok, but there is code repetition.
    return oss;
}
int main() {
    A a{2};
    {// C++98 way
        std::ostringstream oss;
        oss << a;
        std::cout << oss.str() << std::endl; // prints "A(2)", ok"
    }
    {// possible with C++11, because of the rvalue overload
        std::cout << (std::ostringstream() << a).str() << std::endl; //prints "A(2)", ok
    }
}

正如你在c++ 11中看到的,我们可以实现在c++ 98中不能实现的。那就是利用ostringstream(或ofstream)。现在是OP的问题,这两个重载看起来很相似,可以同时加入一个吗?

一种选择是使用通用引用(Ostream&&),并可选择使用enable_if来约束类型。不太优雅。

通过使用这个"真实世界"的例子,我发现如果想为左值ref和右值ref使用相同的代码,因为可能你可以将一个转换为另一个!

std::ostringstream& operator<<(std::ostringstream&& oss, A const& a){
    return operator<<(oss, a);
}

这看起来像一个无限递归函数,但它不是因为oss是一个左值引用(是的,它是一个左值引用,因为它有一个名字)。所以它会调用另一个重载。

你仍然需要编写两个函数,但其中一个函数的代码不需要维护。

总之,如果"it makes sense"©将一个函数同时应用于(非const)左值引用和右值,这也意味着你可以将右值转换为左值,因此你转发到一个函数。请注意,"它是有意义的"取决于上下文和预期代码的含义,我们必须通过显式调用左值重载来"告诉"编译器。

我并不是说这比使用通用引用更好,我是说这是另一种选择,而且可以说意图更明确。

可编辑的代码:http://ideone.com/XSxsvY。(欢迎反馈)