问题
So given the following 2 functions:
int rvalue();
int& lvalue();
The following would be valid:
std::tuple<int&, int> x = appropriate_fn(lvalue(), rvalue());
I was thinking something like it like this:
template <typename T, typename...Ts>
auto make_comparible(T const& arg, Ts&&...args)
{
return std::make_tuple(T(arg), make_comparible(args...));
}
template <typename T, typename...Ts>
auto make_comparible(T& arg, Ts&&...args)
{
return std::make_tuple<T&>(arg, make_comparible(args...));
}
template <typename T>
auto make_comparible(T const& arg)
{
return std::tuple<T>(T(arg));
}
template <typename T>
auto make_comparible(T& arg)
{
return std::tuple<T&>(arg);
}
But there's three issues this that I can see.
This is not a simple
std::tuple
, but a nested one. Which, come to think about it, may not be an issue as I just want to do comparisons (less than, equal to) on it and should still work.This doesn't distinguish between a temporary and a const reference. This is a bit annoying but I don't see any way around it.
Most importantly, it doesn't work. Given the following:
std::tuple<int, std::tuple<int&>> x = make_comparible(rvalue(), lvalue()); std::tuple<int&, std::tuple<int>> y = make_comparible(lvalue(), rvalue());
The first one is works, but the second one give an error because
make_comparible()
is returningstd::tuple<int, std::tuple<int&>>
instead ofstd::tuple<int&, std::tuple<int>>
. Demo
So, is what I'm asking for possible, or is it a pipe dream?
The use case is that I want to define a function in a class that will return a tuple (or otherwise comparable type), which will not result in a dangling pointer/reference, and which will be easy to use.
EDIT
Ok, so after watching C++ and Beyond 2012: Scott Meyers - Universal References in C++11, it looks like overloading on a universal reference is almost always an error. But as I am trying to differentiate a lvalue from an rvalue, irrespective of constness, this would be the right way to go.
I can use overloading to get the universal reference to bind to only rvalues, if I declare overloads which are for lvalues. I had also forgotten to use std::forward()
which was a brain fart on my part. I should have known better.
#include <iostream>
#include <tuple>
// forward declarations
template <typename T, typename...Ts>
decltype(auto) make_comparible(T const& arg, Ts&&...args);
template <typename T, typename...Ts>
decltype(auto) make_comparible(T& arg, Ts&&...args);
template <typename T>
decltype(auto) make_comparible(T&& arg);
template <typename T>
decltype(auto) make_comparible(T const& arg);
template <typename T>
decltype(auto) make_comparible(T& arg);
// rvalue
template <typename T, typename...Ts>
decltype(auto) make_comparible(T&& arg, Ts&&...args)
{
std::cout << "rvalue ";
// want to copy, so do not use std::move()
return std::make_tuple(arg, make_comparible(std::forward<Ts>(args)...));
}
// lvalue const
template <typename T, typename...Ts>
decltype(auto) make_comparible(T const& arg, Ts&&...args)
{
std::cout << "lvalue const ref ";
// This is a reference, so store as a reference
return std::make_tuple<T const&>(arg, make_comparible(std::forward<Ts>(args)...));
}
// lvalue
template <typename T, typename...Ts>
decltype(auto) make_comparible(T& arg, Ts&&...args)
{
std::cout << "lvalue ref ";
// This is a reference, so store as a reference
return std::make_tuple<T&>(arg, make_comparible(std::forward<Ts>(args)...));
}
// rvalue
template <typename T>
decltype(auto) make_comparible(T&& arg)
{
std::cout << "rvalue ";
// want to copy, so do not use std::move()
return std::tuple<T>(arg);
}
// lvalue const
template <typename T>
decltype(auto) make_comparible(T const& arg)
{
std::cout << "lvalue const ref ";
// This is a reference, so store as a reference
return std::tuple<T const&>(arg);
}
// lvalue
template <typename T>
decltype(auto) make_comparible(T& arg)
{
std::cout << "lvalue ref ";
// This is a reference, so store as a reference
return std::tuple<T&>(arg);
}
int var = 5;
int rvalue() { return 4; }
int& lvalue() { return var; }
int const& const_lvalue() { return var; }
int main()
{
// expect output "rvalue lvalue ref", OK
std::tuple<int, std::tuple<int&>> x = make_comparible(rvalue(), lvalue());
std::cout << std::endl;
// expect output "rvalue lvalue const ref", OK
std::tuple<int, std::tuple<int const&>> y = make_comparible(rvalue(), const_lvalue());
std::cout << std::endl;
// expect output "lvalue ref lvalue const ref rvalue", OK
make_comparible(lvalue(), const_lvalue(), rvalue());
// But this doesn't work. Type returned was std::tuple<int, std::tuple<int, std::tuple<int> > >. WHY?
std::tuple<int&, std::tuple<int const&, std::tuple<int>>> z = make_comparible(lvalue(), const_lvalue(), rvalue());
std::cout << std::endl;
return 0;
}
So the code path is correct. But the type returned is wrong. I'm getting a std::tuple<int, std::tuple<int, std::tuple<int>>>
instead of a std::tuple<int&, std::tuple<int const&, std::tuple<int>>>
. WHY?
回答1:
If I understand correctly what do you want, you can use std::reference
(to wrap a l-value reference so that std::make_tuple()
produce std::tuple
with a reference in the corresponding position), and std::forward
, to get the correct type of reference from a variadic list of arguments.
So you can write a couple of convert functions
int rVal ()
{ return 0; }
int & lVal ()
{ static int val { 1 }; return val; }
and make_comparable()
become
template <typename ... Ts>
auto make_comparible (Ts && ... args)
{ return std::make_tuple(convert(std::forward<Ts>(args))...); }
if you can use C++14/C++17 (auto
return type), or
template <typename ... Ts>
auto make_comparible (Ts && ... args)
-> decltype(std::make_tuple(convert(std::forward<Ts>(args))...))
{ return std::make_tuple(convert(std::forward<Ts>(args))...); }
or also (simpler)
template <typename ... Ts>
auto make_comparible (Ts && ... args)
-> decltype(std::make_tuple(convert(std::forward<Ts>(args))...))
{ return { convert(std::forward<Ts>(args))... }; }
if you must use C++11 (auto
plus decltype()
; ugly but works).
The following is a full working (C++14) example.
#include <tuple>
#include <functional>
int rVal ()
{ return 0; }
int & lVal ()
{ static int val { 1 }; return val; }
template <typename T>
std::reference_wrapper<T> convert (T & t)
{ return t; }
template <typename T>
T convert (T && t)
{ return std::move(t); }
template <typename ... Ts>
auto make_comparible (Ts && ... args)
{ return std::make_tuple(convert(std::forward<Ts>(args))...); }
int main ()
{
auto t = make_comparible(rVal(), lVal());
static_assert(std::is_same<std::tuple<int, int&>, decltype(t)>{}, "!");
}
来源:https://stackoverflow.com/questions/46894411/is-there-a-way-to-convert-a-list-of-lvalues-and-rvalues-to-a-tuple-with-referenc