Is there a way to convert a list of lvalues and rvalues to a tuple with reference types and full types respectively?

北城以北 提交于 2020-01-24 12:18:54

问题


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.

  1. 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.

  2. 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.

  3. 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 returning std::tuple<int, std::tuple<int&>> instead of std::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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!