HC4*_*ica 33 c++ function rvalue lvalue c++11
有没有办法在C++中编写一个接受左值和右值参数的函数,而不是模板?
例如,假设我编写了一个print_stream
从a 读取istream
并打印读取到屏幕的数据的函数.
我认为print_stream
像这样打电话是合理的:
fstream file{"filename"};
print_stream(file);
Run Code Online (Sandbox Code Playgroud)
以及像这样:
print_stream(fstream{"filename"});
Run Code Online (Sandbox Code Playgroud)
但是我如何声明print_stream
这两种用途都有效呢?
如果我声明它
void print_stream(istream& is);
Run Code Online (Sandbox Code Playgroud)
然后第二次使用将无法编译,因为rvalue不会绑定到非const左值引用.
如果我声明它
void print_stream(istream&& is);
Run Code Online (Sandbox Code Playgroud)
然后第一次使用将无法编译,因为左值不会绑定到右值引用.
如果我声明它
void print_stream(const istream& is);
Run Code Online (Sandbox Code Playgroud)
那么函数的实现将无法编译,因为你无法读取const istream
.
我不能使该函数成为模板并使用"通用引用",因为它的实现需要单独编译.
我可以提供两个重载:
void print_stream(istream& is);
void print_stream(istream&& is);
Run Code Online (Sandbox Code Playgroud)
并且第二次调用第一次,但这似乎是很多不必要的样板,并且我发现每次用这样的语义编写函数时都必须这样做非常不幸.
我能做些什么吗?
And*_*owl 20
我想说,除了提供两个重载或使你的功能成为模板之外,没有太多理智的选择.
如果你真的,真的需要一个(丑陋的)替代品,那么我猜你唯一的(疯狂的)事情就是让你的函数接受const&
一个前置条件,说你不能将一个合格const
类型的对象传递给它(你不想支持它).然后允许该函数抛弃const
引用的ness.
但我个人会写两个重载并根据另一个定义一个,所以你复制声明,但不是定义:
void foo(X& x)
{
// Here goes the stuff...
}
void foo(X&& x) { foo(x); }
Run Code Online (Sandbox Code Playgroud)
另一个相当丑陋的选择是使函数成为模板并显式实例化两个版本:
template<typename T>
void print(T&&) { /* ... */ }
template void print<istream&>(istream&);
template void print<istream&&>(istream&&);
Run Code Online (Sandbox Code Playgroud)
这可以单独编译.客户端代码只需要声明模板.
不过,我个人只是坚持Andy Prowl的建议.
这是一个可以扩展到任意数量参数的解决方案,并且不需要接受函数是模板。
\n\n#include <utility>\n\ntemplate <typename Ref>\nstruct lvalue_or_rvalue {\n\n Ref &&ref;\n\n template <typename Arg>\n constexpr lvalue_or_rvalue(Arg &&arg) noexcept\n : ref(std::move(arg))\n { }\n\n constexpr operator Ref& () const & noexcept { return ref; }\n constexpr operator Ref&& () const && noexcept { return std::move(ref); }\n constexpr Ref& operator*() const noexcept { return ref; }\n constexpr Ref* operator->() const noexcept { return &ref; }\n\n};\n
Run Code Online (Sandbox Code Playgroud)\n\n#include <fstream>\n#include <iostream>\n\nusing namespace std;\n\nvoid print_stream(lvalue_or_rvalue<istream> is) {\n cout << is->rdbuf();\n}\n\nint main() {\n ifstream file("filename");\n print_stream(file); // call with lvalue\n print_stream(ifstream("filename")); // call with rvalue\n return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n与其他解决方案相比,我更喜欢这个解决方案,因为它很惯用,不需要每次使用它时都编写函数模板,并且它会产生明显的编译器错误,例如\xe2\x80\xa6
\n\n print_stream("filename"); // oops! forgot to construct an ifstream\n
Run Code Online (Sandbox Code Playgroud)\n\n\n\n#include <utility>\n\ntemplate <typename Ref>\nstruct lvalue_or_rvalue {\n\n Ref &&ref;\n\n template <typename Arg>\n constexpr lvalue_or_rvalue(Arg &&arg) noexcept\n : ref(std::move(arg))\n { }\n\n constexpr operator Ref& () const & noexcept { return ref; }\n constexpr operator Ref&& () const && noexcept { return std::move(ref); }\n constexpr Ref& operator*() const noexcept { return ref; }\n constexpr Ref* operator->() const noexcept { return &ref; }\n\n};\n
Run Code Online (Sandbox Code Playgroud)\n\n锦上添花的是,该方案还支持隐式应用用户定义的转换构造函数和转换运算符\xe2\x80\xa6
\n\n#include <cmath>\n\nstruct IntWrapper {\n int value;\n constexpr IntWrapper(int value) noexcept : value(value) { }\n};\n\nstruct DoubleWrapper {\n double value;\n constexpr DoubleWrapper(double value) noexcept : value(value) { }\n};\n\nstruct LongWrapper {\n long value;\n constexpr LongWrapper(long value) noexcept : value(value) { }\n constexpr LongWrapper(const IntWrapper &iw) noexcept : value(iw.value) { }\n constexpr operator DoubleWrapper () const noexcept { return value; }\n};\n\nstatic void square(lvalue_or_rvalue<IntWrapper> iw) {\n iw->value *= iw->value;\n}\n\nstatic void cube(lvalue_or_rvalue<LongWrapper> lw) {\n lw->value *= lw->value * lw->value;\n}\n\nstatic void square_root(lvalue_or_rvalue<DoubleWrapper> dw) {\n dw->value = std::sqrt(dw->value);\n}\n\nvoid examples() {\n // implicit conversion from int to IntWrapper&& via constructor\n square(42);\n\n // implicit conversion from IntWrapper& to LongWrapper&& via constructor\n IntWrapper iw(42);\n cube(iw);\n\n // implicit conversion from IntWrapper&& to LongWrapper&& via constructor\n cube(IntWrapper(42));\n\n // implicit conversion from LongWrapper& to DoubleWrapper&& via operator\n LongWrapper lw(42);\n square_root(lw);\n\n // implicit conversion from LongWrapper&& to DoubleWrapper&& via operator\n square_root(LongWrapper(42));\n}\n
Run Code Online (Sandbox Code Playgroud)\n
// Because of universal reference
// template function with && can catch rvalue and lvalue
// We can use std::is_same to restrict T must be istream
// it's an alternative choice, and i think is's better than two overload functions
template <typename T>
typename std::enable_if<
std::is_same<typename std::decay<T>::type, istream>::value
>::type
print(T&& t) {
// you can get the real value type by forward
// std::forward<T>(t)
}
Run Code Online (Sandbox Code Playgroud)
大胆一点,拥抱通用的前向函数并为它们命名。
template<typename Stream>
auto stream_meh_to(Stream&& s)
->decltype(std::forward<Stream>(s) << std::string{/* */}){
return std::forward<Stream>(s) << std::string{"meh\n"};}
Run Code Online (Sandbox Code Playgroud)
请注意,这将适用于任何对其有意义的工作,而不仅仅是ostream
s。这是一件好事。
如果该函数使用一个没有意义的参数调用,它会简单地忽略这个定义。顺便说一句,如果缩进设置为 4 个空格,这会更好。:)
这与 Cube 的答案相同,除了我说的是,在可能的情况下,不检查特定类型并让泛型编程来做它的事情更优雅。