What should tuple_map return?

Multi tool use
Multi tool use

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP











up vote
12
down vote

favorite
2












I want to implement a generic tuple_map function that takes a functor and an std::tuple, applies the functor to every element of this tuple and returns an std::tuple of results. The implementation is pretty straightforward, however the question arises: what type should this function return? My implementation used std::make_tuple. However, here std::forward_as_tuple was suggested.



To be more specific, the implementation (handling of empty tuples is omitted for brevity):



#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>

template<class Fn, class Tuple, std::size_t... indices>
constexpr auto tuple_map_v(Fn fn, Tuple&& tuple, std::index_sequence<indices...>)

return std::make_tuple(fn(std::get<indices>(std::forward<Tuple>(tuple)))...);
// ^^^


template<class Fn, class Tuple, std::size_t... indices>
constexpr auto tuple_map_r(Fn fn, Tuple&& tuple, std::index_sequence<indices...>)

return std::forward_as_tuple(fn(std::get<indices>(std::forward<Tuple>(tuple)))...);
// ^^^


template<class Tuple, class Fn>
constexpr auto tuple_map_v(Fn fn, Tuple&& tuple)

return tuple_map_v(fn, std::forward<Tuple>(tuple),
std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>);


template<class Tuple, class Fn>
constexpr auto tuple_map_r(Fn fn, Tuple&& tuple)

return tuple_map_r(fn, std::forward<Tuple>(tuple),
std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>);



In the case 1 we use std::make_tuple which decays type of each argument (_v for value), and in the case 2 we use std::forward_as_tuple which preserves references (_r for reference). Both cases have their pros and cons.




  1. Dangling references.



    auto copy = (auto x) return x; ;
    auto const_id = (const auto& x) -> decltype(auto) return x; ;

    auto r1 = tuple_map_v(copy, std::make_tuple(1));
    // OK, type of r1 is std::tuple<int>

    auto r2 = tuple_map_r(copy, std::make_tuple(1));
    // UB, type of r2 is std::tuple<int&&>

    std::tuple<int> r3 = tuple_map_r(copy, std::make_tuple(1));
    // Still UB

    std::tuple<int> r4 = tuple_map_r(const_id, std::make_tuple(1));
    // OK now



  2. Tuple of references.



    auto id = (auto& x) -> decltype(auto) return x; ;

    int a = 0, b = 0;
    auto r1 = tuple_map_v(id, std::forward_as_tuple(a, b));
    // Type of r1 is std::tuple<int, int>
    ++std::get<0>(r1);
    // Increments a copy, a is still zero

    auto r2 = tuple_map_r(id, std::forward_as_tuple(a, b));
    // Type of r2 is std::tuple<int&, int&>
    ++std::get<0>(r2);
    // OK, now a = 1



  3. Move-only types.



    NonCopyable nc;
    auto r1 = tuple_map_v(id, std::forward_as_tuple(nc));
    // Does not compile without a copy constructor

    auto r2 = tuple_map_r(id, std::forward_as_tuple(nc));
    // OK, type of r2 is std::tuple<NonCopyable&>



  4. References with std::make_tuple.



    auto id_ref = (auto& x) return std::reference_wrapper(x); ;

    NonCopyable nc;
    auto r1 = tuple_map_v(id_ref, std::forward_as_tuple(nc));
    // OK now, type of r1 is std::tuple<NonCopyable&>

    auto r2 = tuple_map_v(id_ref, std::forward_as_tuple(a, b));
    // OK, type of r2 is std::tuple<int&, int&>


(Probably, I got something wrong or missed something important.)



It seems that make_tuple is the way to go: it doesn't produce dangling references and still can be forced to deduce a reference type. How would you implement tuple_map (and what would be the pitfalls associated with it)?







share|improve this question




















  • Interesting question. But shouldn't it be the responsibility of the person writing the call to make sure the behavior is well defined? For instance, if we take auto fn = (auto const& x) return std::cref(x); ; auto a = fn(2); We would get a dangling reference, without any tuple involved. My conclusion would be to go with forward_as_tuple knowing that (little) caveat
    – Rerito
    Aug 7 at 13:36











  • Did You think of just making separate overloads/SFINAE versions for lvalues and rvalues? I think this would allow You to get the best of both worlds
    – bartop
    Aug 7 at 13:39














up vote
12
down vote

favorite
2












I want to implement a generic tuple_map function that takes a functor and an std::tuple, applies the functor to every element of this tuple and returns an std::tuple of results. The implementation is pretty straightforward, however the question arises: what type should this function return? My implementation used std::make_tuple. However, here std::forward_as_tuple was suggested.



To be more specific, the implementation (handling of empty tuples is omitted for brevity):



#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>

template<class Fn, class Tuple, std::size_t... indices>
constexpr auto tuple_map_v(Fn fn, Tuple&& tuple, std::index_sequence<indices...>)

return std::make_tuple(fn(std::get<indices>(std::forward<Tuple>(tuple)))...);
// ^^^


template<class Fn, class Tuple, std::size_t... indices>
constexpr auto tuple_map_r(Fn fn, Tuple&& tuple, std::index_sequence<indices...>)

return std::forward_as_tuple(fn(std::get<indices>(std::forward<Tuple>(tuple)))...);
// ^^^


template<class Tuple, class Fn>
constexpr auto tuple_map_v(Fn fn, Tuple&& tuple)

return tuple_map_v(fn, std::forward<Tuple>(tuple),
std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>);


template<class Tuple, class Fn>
constexpr auto tuple_map_r(Fn fn, Tuple&& tuple)

return tuple_map_r(fn, std::forward<Tuple>(tuple),
std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>);



In the case 1 we use std::make_tuple which decays type of each argument (_v for value), and in the case 2 we use std::forward_as_tuple which preserves references (_r for reference). Both cases have their pros and cons.




  1. Dangling references.



    auto copy = (auto x) return x; ;
    auto const_id = (const auto& x) -> decltype(auto) return x; ;

    auto r1 = tuple_map_v(copy, std::make_tuple(1));
    // OK, type of r1 is std::tuple<int>

    auto r2 = tuple_map_r(copy, std::make_tuple(1));
    // UB, type of r2 is std::tuple<int&&>

    std::tuple<int> r3 = tuple_map_r(copy, std::make_tuple(1));
    // Still UB

    std::tuple<int> r4 = tuple_map_r(const_id, std::make_tuple(1));
    // OK now



  2. Tuple of references.



    auto id = (auto& x) -> decltype(auto) return x; ;

    int a = 0, b = 0;
    auto r1 = tuple_map_v(id, std::forward_as_tuple(a, b));
    // Type of r1 is std::tuple<int, int>
    ++std::get<0>(r1);
    // Increments a copy, a is still zero

    auto r2 = tuple_map_r(id, std::forward_as_tuple(a, b));
    // Type of r2 is std::tuple<int&, int&>
    ++std::get<0>(r2);
    // OK, now a = 1



  3. Move-only types.



    NonCopyable nc;
    auto r1 = tuple_map_v(id, std::forward_as_tuple(nc));
    // Does not compile without a copy constructor

    auto r2 = tuple_map_r(id, std::forward_as_tuple(nc));
    // OK, type of r2 is std::tuple<NonCopyable&>



  4. References with std::make_tuple.



    auto id_ref = (auto& x) return std::reference_wrapper(x); ;

    NonCopyable nc;
    auto r1 = tuple_map_v(id_ref, std::forward_as_tuple(nc));
    // OK now, type of r1 is std::tuple<NonCopyable&>

    auto r2 = tuple_map_v(id_ref, std::forward_as_tuple(a, b));
    // OK, type of r2 is std::tuple<int&, int&>


(Probably, I got something wrong or missed something important.)



It seems that make_tuple is the way to go: it doesn't produce dangling references and still can be forced to deduce a reference type. How would you implement tuple_map (and what would be the pitfalls associated with it)?







share|improve this question




















  • Interesting question. But shouldn't it be the responsibility of the person writing the call to make sure the behavior is well defined? For instance, if we take auto fn = (auto const& x) return std::cref(x); ; auto a = fn(2); We would get a dangling reference, without any tuple involved. My conclusion would be to go with forward_as_tuple knowing that (little) caveat
    – Rerito
    Aug 7 at 13:36











  • Did You think of just making separate overloads/SFINAE versions for lvalues and rvalues? I think this would allow You to get the best of both worlds
    – bartop
    Aug 7 at 13:39












up vote
12
down vote

favorite
2









up vote
12
down vote

favorite
2






2





I want to implement a generic tuple_map function that takes a functor and an std::tuple, applies the functor to every element of this tuple and returns an std::tuple of results. The implementation is pretty straightforward, however the question arises: what type should this function return? My implementation used std::make_tuple. However, here std::forward_as_tuple was suggested.



To be more specific, the implementation (handling of empty tuples is omitted for brevity):



#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>

template<class Fn, class Tuple, std::size_t... indices>
constexpr auto tuple_map_v(Fn fn, Tuple&& tuple, std::index_sequence<indices...>)

return std::make_tuple(fn(std::get<indices>(std::forward<Tuple>(tuple)))...);
// ^^^


template<class Fn, class Tuple, std::size_t... indices>
constexpr auto tuple_map_r(Fn fn, Tuple&& tuple, std::index_sequence<indices...>)

return std::forward_as_tuple(fn(std::get<indices>(std::forward<Tuple>(tuple)))...);
// ^^^


template<class Tuple, class Fn>
constexpr auto tuple_map_v(Fn fn, Tuple&& tuple)

return tuple_map_v(fn, std::forward<Tuple>(tuple),
std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>);


template<class Tuple, class Fn>
constexpr auto tuple_map_r(Fn fn, Tuple&& tuple)

return tuple_map_r(fn, std::forward<Tuple>(tuple),
std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>);



In the case 1 we use std::make_tuple which decays type of each argument (_v for value), and in the case 2 we use std::forward_as_tuple which preserves references (_r for reference). Both cases have their pros and cons.




  1. Dangling references.



    auto copy = (auto x) return x; ;
    auto const_id = (const auto& x) -> decltype(auto) return x; ;

    auto r1 = tuple_map_v(copy, std::make_tuple(1));
    // OK, type of r1 is std::tuple<int>

    auto r2 = tuple_map_r(copy, std::make_tuple(1));
    // UB, type of r2 is std::tuple<int&&>

    std::tuple<int> r3 = tuple_map_r(copy, std::make_tuple(1));
    // Still UB

    std::tuple<int> r4 = tuple_map_r(const_id, std::make_tuple(1));
    // OK now



  2. Tuple of references.



    auto id = (auto& x) -> decltype(auto) return x; ;

    int a = 0, b = 0;
    auto r1 = tuple_map_v(id, std::forward_as_tuple(a, b));
    // Type of r1 is std::tuple<int, int>
    ++std::get<0>(r1);
    // Increments a copy, a is still zero

    auto r2 = tuple_map_r(id, std::forward_as_tuple(a, b));
    // Type of r2 is std::tuple<int&, int&>
    ++std::get<0>(r2);
    // OK, now a = 1



  3. Move-only types.



    NonCopyable nc;
    auto r1 = tuple_map_v(id, std::forward_as_tuple(nc));
    // Does not compile without a copy constructor

    auto r2 = tuple_map_r(id, std::forward_as_tuple(nc));
    // OK, type of r2 is std::tuple<NonCopyable&>



  4. References with std::make_tuple.



    auto id_ref = (auto& x) return std::reference_wrapper(x); ;

    NonCopyable nc;
    auto r1 = tuple_map_v(id_ref, std::forward_as_tuple(nc));
    // OK now, type of r1 is std::tuple<NonCopyable&>

    auto r2 = tuple_map_v(id_ref, std::forward_as_tuple(a, b));
    // OK, type of r2 is std::tuple<int&, int&>


(Probably, I got something wrong or missed something important.)



It seems that make_tuple is the way to go: it doesn't produce dangling references and still can be forced to deduce a reference type. How would you implement tuple_map (and what would be the pitfalls associated with it)?







share|improve this question












I want to implement a generic tuple_map function that takes a functor and an std::tuple, applies the functor to every element of this tuple and returns an std::tuple of results. The implementation is pretty straightforward, however the question arises: what type should this function return? My implementation used std::make_tuple. However, here std::forward_as_tuple was suggested.



To be more specific, the implementation (handling of empty tuples is omitted for brevity):



#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>

template<class Fn, class Tuple, std::size_t... indices>
constexpr auto tuple_map_v(Fn fn, Tuple&& tuple, std::index_sequence<indices...>)

return std::make_tuple(fn(std::get<indices>(std::forward<Tuple>(tuple)))...);
// ^^^


template<class Fn, class Tuple, std::size_t... indices>
constexpr auto tuple_map_r(Fn fn, Tuple&& tuple, std::index_sequence<indices...>)

return std::forward_as_tuple(fn(std::get<indices>(std::forward<Tuple>(tuple)))...);
// ^^^


template<class Tuple, class Fn>
constexpr auto tuple_map_v(Fn fn, Tuple&& tuple)

return tuple_map_v(fn, std::forward<Tuple>(tuple),
std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>);


template<class Tuple, class Fn>
constexpr auto tuple_map_r(Fn fn, Tuple&& tuple)

return tuple_map_r(fn, std::forward<Tuple>(tuple),
std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>);



In the case 1 we use std::make_tuple which decays type of each argument (_v for value), and in the case 2 we use std::forward_as_tuple which preserves references (_r for reference). Both cases have their pros and cons.




  1. Dangling references.



    auto copy = (auto x) return x; ;
    auto const_id = (const auto& x) -> decltype(auto) return x; ;

    auto r1 = tuple_map_v(copy, std::make_tuple(1));
    // OK, type of r1 is std::tuple<int>

    auto r2 = tuple_map_r(copy, std::make_tuple(1));
    // UB, type of r2 is std::tuple<int&&>

    std::tuple<int> r3 = tuple_map_r(copy, std::make_tuple(1));
    // Still UB

    std::tuple<int> r4 = tuple_map_r(const_id, std::make_tuple(1));
    // OK now



  2. Tuple of references.



    auto id = (auto& x) -> decltype(auto) return x; ;

    int a = 0, b = 0;
    auto r1 = tuple_map_v(id, std::forward_as_tuple(a, b));
    // Type of r1 is std::tuple<int, int>
    ++std::get<0>(r1);
    // Increments a copy, a is still zero

    auto r2 = tuple_map_r(id, std::forward_as_tuple(a, b));
    // Type of r2 is std::tuple<int&, int&>
    ++std::get<0>(r2);
    // OK, now a = 1



  3. Move-only types.



    NonCopyable nc;
    auto r1 = tuple_map_v(id, std::forward_as_tuple(nc));
    // Does not compile without a copy constructor

    auto r2 = tuple_map_r(id, std::forward_as_tuple(nc));
    // OK, type of r2 is std::tuple<NonCopyable&>



  4. References with std::make_tuple.



    auto id_ref = (auto& x) return std::reference_wrapper(x); ;

    NonCopyable nc;
    auto r1 = tuple_map_v(id_ref, std::forward_as_tuple(nc));
    // OK now, type of r1 is std::tuple<NonCopyable&>

    auto r2 = tuple_map_v(id_ref, std::forward_as_tuple(a, b));
    // OK, type of r2 is std::tuple<int&, int&>


(Probably, I got something wrong or missed something important.)



It seems that make_tuple is the way to go: it doesn't produce dangling references and still can be forced to deduce a reference type. How would you implement tuple_map (and what would be the pitfalls associated with it)?









share|improve this question











share|improve this question




share|improve this question










asked Aug 7 at 13:26









Evgeny

1,135822




1,135822











  • Interesting question. But shouldn't it be the responsibility of the person writing the call to make sure the behavior is well defined? For instance, if we take auto fn = (auto const& x) return std::cref(x); ; auto a = fn(2); We would get a dangling reference, without any tuple involved. My conclusion would be to go with forward_as_tuple knowing that (little) caveat
    – Rerito
    Aug 7 at 13:36











  • Did You think of just making separate overloads/SFINAE versions for lvalues and rvalues? I think this would allow You to get the best of both worlds
    – bartop
    Aug 7 at 13:39
















  • Interesting question. But shouldn't it be the responsibility of the person writing the call to make sure the behavior is well defined? For instance, if we take auto fn = (auto const& x) return std::cref(x); ; auto a = fn(2); We would get a dangling reference, without any tuple involved. My conclusion would be to go with forward_as_tuple knowing that (little) caveat
    – Rerito
    Aug 7 at 13:36











  • Did You think of just making separate overloads/SFINAE versions for lvalues and rvalues? I think this would allow You to get the best of both worlds
    – bartop
    Aug 7 at 13:39















Interesting question. But shouldn't it be the responsibility of the person writing the call to make sure the behavior is well defined? For instance, if we take auto fn = (auto const& x) return std::cref(x); ; auto a = fn(2); We would get a dangling reference, without any tuple involved. My conclusion would be to go with forward_as_tuple knowing that (little) caveat
– Rerito
Aug 7 at 13:36





Interesting question. But shouldn't it be the responsibility of the person writing the call to make sure the behavior is well defined? For instance, if we take auto fn = (auto const& x) return std::cref(x); ; auto a = fn(2); We would get a dangling reference, without any tuple involved. My conclusion would be to go with forward_as_tuple knowing that (little) caveat
– Rerito
Aug 7 at 13:36













Did You think of just making separate overloads/SFINAE versions for lvalues and rvalues? I think this would allow You to get the best of both worlds
– bartop
Aug 7 at 13:39




Did You think of just making separate overloads/SFINAE versions for lvalues and rvalues? I think this would allow You to get the best of both worlds
– bartop
Aug 7 at 13:39












1 Answer
1






active

oldest

votes

















up vote
7
down vote



accepted










The problem you highlighted in your question is that using std::forward_as_tuple on a functor that returns by value will leave you with an rvalue reference in the resulting tuple.



By using make_tuple you cannot keep lvalue-refs, however by using forward_as_tuple, you cannot keep plain values. You can instead rely on std::invoke_result to find out what are the types your result tuple must hold and use the appropriate std::tuple constructor.



template<class Fn, class Tuple, std::size_t... indices>
constexpr auto tuple_map_r(Fn fn, Tuple&& tuple, std::index_sequence<indices...>)
using tuple_type = std::tuple<
typename std::invoke_result<
Fn, decltype(std::get<indices>(std::forward<Tuple>(tuple)))
>::type...
>;
return tuple_type(fn(std::get<indices>(std::forward<Tuple>(tuple)))...);



This way you preserve the value category of the result of the fn call.
Live demo on Coliru






share|improve this answer




















  • Am I correct that with template argument deduction we don't need tuple_type and can simply return std::tuple(fn(...)...)? I even wanted to include this alternative into my question, but (erroneously) thought it was equivalent to forward_as_tuple.
    – Evgeny
    Aug 7 at 14:26










  • @Evgeny Correct. With template deduction guides it would work I think (I am not too confident since I am not familiar with them yet though...).
    – Rerito
    Aug 7 at 14:28






  • 1




    @Evgeny after some testing, it appears that the deduction guides for std::tuple decay their arguments... So explicit type is still the only way to go it seems!
    – Rerito
    Aug 7 at 14:33










  • I've just made a similar test and was going to write a comment that std::tuple(...) doesn't really work. :)
    – Evgeny
    Aug 7 at 14:38






  • 4




    @Evgeny tuple(x...) is equivalent to make_tuple(x...), with the exception that reference_wrapper<T> stays as reference_wrapper<T> and doesn't become T&.
    – Barry
    Aug 7 at 15:07










Your Answer





StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
convertImagesToLinks: true,
noModals: false,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);








 

draft saved


draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f51727961%2fwhat-should-tuple-map-return%23new-answer', 'question_page');

);

Post as a guest






























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes








up vote
7
down vote



accepted










The problem you highlighted in your question is that using std::forward_as_tuple on a functor that returns by value will leave you with an rvalue reference in the resulting tuple.



By using make_tuple you cannot keep lvalue-refs, however by using forward_as_tuple, you cannot keep plain values. You can instead rely on std::invoke_result to find out what are the types your result tuple must hold and use the appropriate std::tuple constructor.



template<class Fn, class Tuple, std::size_t... indices>
constexpr auto tuple_map_r(Fn fn, Tuple&& tuple, std::index_sequence<indices...>)
using tuple_type = std::tuple<
typename std::invoke_result<
Fn, decltype(std::get<indices>(std::forward<Tuple>(tuple)))
>::type...
>;
return tuple_type(fn(std::get<indices>(std::forward<Tuple>(tuple)))...);



This way you preserve the value category of the result of the fn call.
Live demo on Coliru






share|improve this answer




















  • Am I correct that with template argument deduction we don't need tuple_type and can simply return std::tuple(fn(...)...)? I even wanted to include this alternative into my question, but (erroneously) thought it was equivalent to forward_as_tuple.
    – Evgeny
    Aug 7 at 14:26










  • @Evgeny Correct. With template deduction guides it would work I think (I am not too confident since I am not familiar with them yet though...).
    – Rerito
    Aug 7 at 14:28






  • 1




    @Evgeny after some testing, it appears that the deduction guides for std::tuple decay their arguments... So explicit type is still the only way to go it seems!
    – Rerito
    Aug 7 at 14:33










  • I've just made a similar test and was going to write a comment that std::tuple(...) doesn't really work. :)
    – Evgeny
    Aug 7 at 14:38






  • 4




    @Evgeny tuple(x...) is equivalent to make_tuple(x...), with the exception that reference_wrapper<T> stays as reference_wrapper<T> and doesn't become T&.
    – Barry
    Aug 7 at 15:07














up vote
7
down vote



accepted










The problem you highlighted in your question is that using std::forward_as_tuple on a functor that returns by value will leave you with an rvalue reference in the resulting tuple.



By using make_tuple you cannot keep lvalue-refs, however by using forward_as_tuple, you cannot keep plain values. You can instead rely on std::invoke_result to find out what are the types your result tuple must hold and use the appropriate std::tuple constructor.



template<class Fn, class Tuple, std::size_t... indices>
constexpr auto tuple_map_r(Fn fn, Tuple&& tuple, std::index_sequence<indices...>)
using tuple_type = std::tuple<
typename std::invoke_result<
Fn, decltype(std::get<indices>(std::forward<Tuple>(tuple)))
>::type...
>;
return tuple_type(fn(std::get<indices>(std::forward<Tuple>(tuple)))...);



This way you preserve the value category of the result of the fn call.
Live demo on Coliru






share|improve this answer




















  • Am I correct that with template argument deduction we don't need tuple_type and can simply return std::tuple(fn(...)...)? I even wanted to include this alternative into my question, but (erroneously) thought it was equivalent to forward_as_tuple.
    – Evgeny
    Aug 7 at 14:26










  • @Evgeny Correct. With template deduction guides it would work I think (I am not too confident since I am not familiar with them yet though...).
    – Rerito
    Aug 7 at 14:28






  • 1




    @Evgeny after some testing, it appears that the deduction guides for std::tuple decay their arguments... So explicit type is still the only way to go it seems!
    – Rerito
    Aug 7 at 14:33










  • I've just made a similar test and was going to write a comment that std::tuple(...) doesn't really work. :)
    – Evgeny
    Aug 7 at 14:38






  • 4




    @Evgeny tuple(x...) is equivalent to make_tuple(x...), with the exception that reference_wrapper<T> stays as reference_wrapper<T> and doesn't become T&.
    – Barry
    Aug 7 at 15:07












up vote
7
down vote



accepted







up vote
7
down vote



accepted






The problem you highlighted in your question is that using std::forward_as_tuple on a functor that returns by value will leave you with an rvalue reference in the resulting tuple.



By using make_tuple you cannot keep lvalue-refs, however by using forward_as_tuple, you cannot keep plain values. You can instead rely on std::invoke_result to find out what are the types your result tuple must hold and use the appropriate std::tuple constructor.



template<class Fn, class Tuple, std::size_t... indices>
constexpr auto tuple_map_r(Fn fn, Tuple&& tuple, std::index_sequence<indices...>)
using tuple_type = std::tuple<
typename std::invoke_result<
Fn, decltype(std::get<indices>(std::forward<Tuple>(tuple)))
>::type...
>;
return tuple_type(fn(std::get<indices>(std::forward<Tuple>(tuple)))...);



This way you preserve the value category of the result of the fn call.
Live demo on Coliru






share|improve this answer












The problem you highlighted in your question is that using std::forward_as_tuple on a functor that returns by value will leave you with an rvalue reference in the resulting tuple.



By using make_tuple you cannot keep lvalue-refs, however by using forward_as_tuple, you cannot keep plain values. You can instead rely on std::invoke_result to find out what are the types your result tuple must hold and use the appropriate std::tuple constructor.



template<class Fn, class Tuple, std::size_t... indices>
constexpr auto tuple_map_r(Fn fn, Tuple&& tuple, std::index_sequence<indices...>)
using tuple_type = std::tuple<
typename std::invoke_result<
Fn, decltype(std::get<indices>(std::forward<Tuple>(tuple)))
>::type...
>;
return tuple_type(fn(std::get<indices>(std::forward<Tuple>(tuple)))...);



This way you preserve the value category of the result of the fn call.
Live demo on Coliru







share|improve this answer












share|improve this answer



share|improve this answer










answered Aug 7 at 14:07









Rerito

4,6081241




4,6081241











  • Am I correct that with template argument deduction we don't need tuple_type and can simply return std::tuple(fn(...)...)? I even wanted to include this alternative into my question, but (erroneously) thought it was equivalent to forward_as_tuple.
    – Evgeny
    Aug 7 at 14:26










  • @Evgeny Correct. With template deduction guides it would work I think (I am not too confident since I am not familiar with them yet though...).
    – Rerito
    Aug 7 at 14:28






  • 1




    @Evgeny after some testing, it appears that the deduction guides for std::tuple decay their arguments... So explicit type is still the only way to go it seems!
    – Rerito
    Aug 7 at 14:33










  • I've just made a similar test and was going to write a comment that std::tuple(...) doesn't really work. :)
    – Evgeny
    Aug 7 at 14:38






  • 4




    @Evgeny tuple(x...) is equivalent to make_tuple(x...), with the exception that reference_wrapper<T> stays as reference_wrapper<T> and doesn't become T&.
    – Barry
    Aug 7 at 15:07
















  • Am I correct that with template argument deduction we don't need tuple_type and can simply return std::tuple(fn(...)...)? I even wanted to include this alternative into my question, but (erroneously) thought it was equivalent to forward_as_tuple.
    – Evgeny
    Aug 7 at 14:26










  • @Evgeny Correct. With template deduction guides it would work I think (I am not too confident since I am not familiar with them yet though...).
    – Rerito
    Aug 7 at 14:28






  • 1




    @Evgeny after some testing, it appears that the deduction guides for std::tuple decay their arguments... So explicit type is still the only way to go it seems!
    – Rerito
    Aug 7 at 14:33










  • I've just made a similar test and was going to write a comment that std::tuple(...) doesn't really work. :)
    – Evgeny
    Aug 7 at 14:38






  • 4




    @Evgeny tuple(x...) is equivalent to make_tuple(x...), with the exception that reference_wrapper<T> stays as reference_wrapper<T> and doesn't become T&.
    – Barry
    Aug 7 at 15:07















Am I correct that with template argument deduction we don't need tuple_type and can simply return std::tuple(fn(...)...)? I even wanted to include this alternative into my question, but (erroneously) thought it was equivalent to forward_as_tuple.
– Evgeny
Aug 7 at 14:26




Am I correct that with template argument deduction we don't need tuple_type and can simply return std::tuple(fn(...)...)? I even wanted to include this alternative into my question, but (erroneously) thought it was equivalent to forward_as_tuple.
– Evgeny
Aug 7 at 14:26












@Evgeny Correct. With template deduction guides it would work I think (I am not too confident since I am not familiar with them yet though...).
– Rerito
Aug 7 at 14:28




@Evgeny Correct. With template deduction guides it would work I think (I am not too confident since I am not familiar with them yet though...).
– Rerito
Aug 7 at 14:28




1




1




@Evgeny after some testing, it appears that the deduction guides for std::tuple decay their arguments... So explicit type is still the only way to go it seems!
– Rerito
Aug 7 at 14:33




@Evgeny after some testing, it appears that the deduction guides for std::tuple decay their arguments... So explicit type is still the only way to go it seems!
– Rerito
Aug 7 at 14:33












I've just made a similar test and was going to write a comment that std::tuple(...) doesn't really work. :)
– Evgeny
Aug 7 at 14:38




I've just made a similar test and was going to write a comment that std::tuple(...) doesn't really work. :)
– Evgeny
Aug 7 at 14:38




4




4




@Evgeny tuple(x...) is equivalent to make_tuple(x...), with the exception that reference_wrapper<T> stays as reference_wrapper<T> and doesn't become T&.
– Barry
Aug 7 at 15:07




@Evgeny tuple(x...) is equivalent to make_tuple(x...), with the exception that reference_wrapper<T> stays as reference_wrapper<T> and doesn't become T&.
– Barry
Aug 7 at 15:07












 

draft saved


draft discarded


























 


draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f51727961%2fwhat-should-tuple-map-return%23new-answer', 'question_page');

);

Post as a guest













































































3Kr5,PLsVZ35L2QRtc7 Vhn
GD k4re1GgGplX1qlAH crUk6iiWv,98cqkrQHiOUsBD,21omSp0WgL V0,cMalzCHP6I6yzmXy2q,GeH6KkKVb4

Popular posts from this blog

How to check contact read email or not when send email to Individual?

How many registers does an x86_64 CPU actually have?

Displaying single band from multi-band raster using QGIS