What should tuple_map return?

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













































































Popular posts from this blog

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

Bahrain

Postfix configuration issue with fips on centos 7; mailgun relay