C++ different minmax implementation

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











up vote
9
down vote

favorite












As You may (not) know using std::minmax with auto and temporary arguments may be dangerous. Following code for example is UB because std::minmax returns pair of references, not values:



auto fun()
auto res = std::minmax(3, 4);
return res.first;



I would like to ask if there is possibility to make std::minmax function act safely or at least safer without any overhead? I came up with a solution like this, but I am not completely sure if it is equivalent to current minmax as generated assembly is different for stl-like implementation and mine. So the question is: what are the possible problems/drawbacks of my implementation of minmax in relation to std-like one:



//below is std-like minmax
template< class T >
constexpr std::pair<const T&,const T&> std_minmax( const T& a, const T& b )
return (b < a) ? std::pair<const T&, const T&>(b, a)
: std::pair<const T&, const T&>(a, b);


//below is my minmax implementation
template< class T >
constexpr std::pair<T, T> my_minmax( T&& a, T&& b )
return (b < a) ? std::pair<T, T>(std::forward<T>(b), std::forward<T>(a))
: std::pair<T, T>(std::forward<T>(a), std::forward<T>(b));



Live demo at godbolt.org










share|improve this question























  • Non-const references prefers second overload. Mixed lvalue/rvalue have trouble deducing. And probably a lot more.
    – Passer By
    3 hours ago











  • @PasserBy I am not suggesting using both of them. I am asking about unexpected behaviour or performance differences between the first and second one. The mixed lval/rval one is true though, how could it me mitigated?
    – bartop
    3 hours ago







  • 1




    @DanielLangr: There is no point comparing minmax for int without inline, because it will be inlined.
    – geza
    2 hours ago






  • 1




    With 2 different template arguments, you have the issue of return value type (yes std::common_type might help but constness has to be handled too, and result it might be surprising/non intuitive)...
    – Jarod42
    2 hours ago






  • 1




    IIUC, you want the resulting pair member to be 1) a (const) lvalue reference to the corresponding function argument, if this is an lvalue or 2) a value moved from that argument, if it is an rvalue. Is it right? Just to clarify.
    – Daniel Langr
    2 hours ago














up vote
9
down vote

favorite












As You may (not) know using std::minmax with auto and temporary arguments may be dangerous. Following code for example is UB because std::minmax returns pair of references, not values:



auto fun()
auto res = std::minmax(3, 4);
return res.first;



I would like to ask if there is possibility to make std::minmax function act safely or at least safer without any overhead? I came up with a solution like this, but I am not completely sure if it is equivalent to current minmax as generated assembly is different for stl-like implementation and mine. So the question is: what are the possible problems/drawbacks of my implementation of minmax in relation to std-like one:



//below is std-like minmax
template< class T >
constexpr std::pair<const T&,const T&> std_minmax( const T& a, const T& b )
return (b < a) ? std::pair<const T&, const T&>(b, a)
: std::pair<const T&, const T&>(a, b);


//below is my minmax implementation
template< class T >
constexpr std::pair<T, T> my_minmax( T&& a, T&& b )
return (b < a) ? std::pair<T, T>(std::forward<T>(b), std::forward<T>(a))
: std::pair<T, T>(std::forward<T>(a), std::forward<T>(b));



Live demo at godbolt.org










share|improve this question























  • Non-const references prefers second overload. Mixed lvalue/rvalue have trouble deducing. And probably a lot more.
    – Passer By
    3 hours ago











  • @PasserBy I am not suggesting using both of them. I am asking about unexpected behaviour or performance differences between the first and second one. The mixed lval/rval one is true though, how could it me mitigated?
    – bartop
    3 hours ago







  • 1




    @DanielLangr: There is no point comparing minmax for int without inline, because it will be inlined.
    – geza
    2 hours ago






  • 1




    With 2 different template arguments, you have the issue of return value type (yes std::common_type might help but constness has to be handled too, and result it might be surprising/non intuitive)...
    – Jarod42
    2 hours ago






  • 1




    IIUC, you want the resulting pair member to be 1) a (const) lvalue reference to the corresponding function argument, if this is an lvalue or 2) a value moved from that argument, if it is an rvalue. Is it right? Just to clarify.
    – Daniel Langr
    2 hours ago












up vote
9
down vote

favorite









up vote
9
down vote

favorite











As You may (not) know using std::minmax with auto and temporary arguments may be dangerous. Following code for example is UB because std::minmax returns pair of references, not values:



auto fun()
auto res = std::minmax(3, 4);
return res.first;



I would like to ask if there is possibility to make std::minmax function act safely or at least safer without any overhead? I came up with a solution like this, but I am not completely sure if it is equivalent to current minmax as generated assembly is different for stl-like implementation and mine. So the question is: what are the possible problems/drawbacks of my implementation of minmax in relation to std-like one:



//below is std-like minmax
template< class T >
constexpr std::pair<const T&,const T&> std_minmax( const T& a, const T& b )
return (b < a) ? std::pair<const T&, const T&>(b, a)
: std::pair<const T&, const T&>(a, b);


//below is my minmax implementation
template< class T >
constexpr std::pair<T, T> my_minmax( T&& a, T&& b )
return (b < a) ? std::pair<T, T>(std::forward<T>(b), std::forward<T>(a))
: std::pair<T, T>(std::forward<T>(a), std::forward<T>(b));



Live demo at godbolt.org










share|improve this question















As You may (not) know using std::minmax with auto and temporary arguments may be dangerous. Following code for example is UB because std::minmax returns pair of references, not values:



auto fun()
auto res = std::minmax(3, 4);
return res.first;



I would like to ask if there is possibility to make std::minmax function act safely or at least safer without any overhead? I came up with a solution like this, but I am not completely sure if it is equivalent to current minmax as generated assembly is different for stl-like implementation and mine. So the question is: what are the possible problems/drawbacks of my implementation of minmax in relation to std-like one:



//below is std-like minmax
template< class T >
constexpr std::pair<const T&,const T&> std_minmax( const T& a, const T& b )
return (b < a) ? std::pair<const T&, const T&>(b, a)
: std::pair<const T&, const T&>(a, b);


//below is my minmax implementation
template< class T >
constexpr std::pair<T, T> my_minmax( T&& a, T&& b )
return (b < a) ? std::pair<T, T>(std::forward<T>(b), std::forward<T>(a))
: std::pair<T, T>(std::forward<T>(a), std::forward<T>(b));



Live demo at godbolt.org







c++ c++11 stl






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited 2 hours ago

























asked 3 hours ago









bartop

2,369824




2,369824











  • Non-const references prefers second overload. Mixed lvalue/rvalue have trouble deducing. And probably a lot more.
    – Passer By
    3 hours ago











  • @PasserBy I am not suggesting using both of them. I am asking about unexpected behaviour or performance differences between the first and second one. The mixed lval/rval one is true though, how could it me mitigated?
    – bartop
    3 hours ago







  • 1




    @DanielLangr: There is no point comparing minmax for int without inline, because it will be inlined.
    – geza
    2 hours ago






  • 1




    With 2 different template arguments, you have the issue of return value type (yes std::common_type might help but constness has to be handled too, and result it might be surprising/non intuitive)...
    – Jarod42
    2 hours ago






  • 1




    IIUC, you want the resulting pair member to be 1) a (const) lvalue reference to the corresponding function argument, if this is an lvalue or 2) a value moved from that argument, if it is an rvalue. Is it right? Just to clarify.
    – Daniel Langr
    2 hours ago
















  • Non-const references prefers second overload. Mixed lvalue/rvalue have trouble deducing. And probably a lot more.
    – Passer By
    3 hours ago











  • @PasserBy I am not suggesting using both of them. I am asking about unexpected behaviour or performance differences between the first and second one. The mixed lval/rval one is true though, how could it me mitigated?
    – bartop
    3 hours ago







  • 1




    @DanielLangr: There is no point comparing minmax for int without inline, because it will be inlined.
    – geza
    2 hours ago






  • 1




    With 2 different template arguments, you have the issue of return value type (yes std::common_type might help but constness has to be handled too, and result it might be surprising/non intuitive)...
    – Jarod42
    2 hours ago






  • 1




    IIUC, you want the resulting pair member to be 1) a (const) lvalue reference to the corresponding function argument, if this is an lvalue or 2) a value moved from that argument, if it is an rvalue. Is it right? Just to clarify.
    – Daniel Langr
    2 hours ago















Non-const references prefers second overload. Mixed lvalue/rvalue have trouble deducing. And probably a lot more.
– Passer By
3 hours ago





Non-const references prefers second overload. Mixed lvalue/rvalue have trouble deducing. And probably a lot more.
– Passer By
3 hours ago













@PasserBy I am not suggesting using both of them. I am asking about unexpected behaviour or performance differences between the first and second one. The mixed lval/rval one is true though, how could it me mitigated?
– bartop
3 hours ago





@PasserBy I am not suggesting using both of them. I am asking about unexpected behaviour or performance differences between the first and second one. The mixed lval/rval one is true though, how could it me mitigated?
– bartop
3 hours ago





1




1




@DanielLangr: There is no point comparing minmax for int without inline, because it will be inlined.
– geza
2 hours ago




@DanielLangr: There is no point comparing minmax for int without inline, because it will be inlined.
– geza
2 hours ago




1




1




With 2 different template arguments, you have the issue of return value type (yes std::common_type might help but constness has to be handled too, and result it might be surprising/non intuitive)...
– Jarod42
2 hours ago




With 2 different template arguments, you have the issue of return value type (yes std::common_type might help but constness has to be handled too, and result it might be surprising/non intuitive)...
– Jarod42
2 hours ago




1




1




IIUC, you want the resulting pair member to be 1) a (const) lvalue reference to the corresponding function argument, if this is an lvalue or 2) a value moved from that argument, if it is an rvalue. Is it right? Just to clarify.
– Daniel Langr
2 hours ago




IIUC, you want the resulting pair member to be 1) a (const) lvalue reference to the corresponding function argument, if this is an lvalue or 2) a value moved from that argument, if it is an rvalue. Is it right? Just to clarify.
– Daniel Langr
2 hours ago












3 Answers
3






active

oldest

votes

















up vote
2
down vote













One solution is when T is an r-value reference then copy it instead of returning an r-value reference:



#include <utility>

template<class T>
std::pair<T, T> minmax(T&& a, T&& b)
if(a < b)
return a, b;
return b, a;



When the argument is an r-value reference T is deduced as a non-reference type:



int main() 
int a = 1;
int const b = 2;
minmax(1, 1); // std::pair<int, int>
minmax(a, a); // std::pair<int&, int&>
minmax(b, b); // std::pair<const int&, const int&>






share|improve this answer






















  • Note that this will not work with mixed rvalue/lvalue arguments (template deduction for T will fail then). It's the same as for OP's solution, but he/she asked for a better alternative to std::minmax, so I would suppose he/she actually wants to support mixed-category arguments.
    – Daniel Langr
    12 mins ago










  • @DanielLangr It doesn't support mixed rvalue/lvalue arguments by design.
    – Maxim Egorushkin
    10 mins ago

















up vote
1
down vote













I am not exactly sure what are you trying to achieve. You wrote:




without any overhead




but your solution will copy lvalue arguments. Is it what you want?



Anyway, you cannot use two forwarding references with the same template parameter this way, since it will fail if both function arguments have different categories:



template <typename T> void f(T&& a, T&& b) 

int main()
int a = 3;
f(a, 1); // error: template argument deduction/substitution failed



For the first function argument, T would be deduced as int&, and for second as int.




If you want to remove any copying, the only possibility is the member of the resulting pair to be:



  1. a (const) lvalue reference to the corresponding function argument in case it is an lvalue,


  2. a value moved from that argument if is it an rvalue.


I don't think this is possible to achieve. Consider:



std::string a("hello");
auto p = minmax(a, std::string("world"));


Here the resulting type would be std::pair<std::string&, std::string>. However, in case of



auto p = minmax(a, std::string("earth"));


the resulting type would be different, namely std::pair<std::string, std::string&>.



Therefore, the resulting type would depend on a runtime condition (which generally requires runtime polymorphism).




UPDATE



Out of curiosity, I just came up with a wrapper that can hold some object either by (const) reference or by value:



template <typename T>
class ref_val
std::unique_ptr<T> val_;
const T& ref_;
public:
ref_val() = delete;
ref_val(const T& ref) : ref_(ref)
ref_val(T&& val) : val_(new T(std::move(val))), ref_(*val_)
ref_val(const ref_val&) = delete;
ref_val(ref_val&& other) : val_(std::move(other.val_)), ref_(val_ ? *val_ : other.ref_)
const T& get() const return ref_;
... // assignment operators, ...
;


Note that it adds another overhead in the form of dynamic allocation. A solution based on std::aligned_storage and placement new would be more efficient.



With that, you can define minmax as:



template <typename T, typename U,
typename V = std::enable_if_t<std::is_same_v<std::decay_t<T>, std::decay_t<U>>, std::decay_t<T>>>
std::pair<ref_val<V>, ref_val<V>> minmax(T&& a, U&& b)
if (b < a) return std::forward<U>(b), std::forward<T>(a) ;
else return std::forward<T>(a), std::forward<U>(b) ;



Live demo is here: https://wandbox.org/permlink/FBNjB3gxnYUmMSwR



This is very basic implementation, but it should prevent copying both from lvalue and rvalue arguments of minmax.






share|improve this answer





























    up vote
    1
    down vote













    With C++17 it is possible to use constexpr if to tie lvalue args and copy everything else. With C++11 I would probably think twice before building an angle brackets moster with a scary look for such a simple use case.



    godbolt, coliru



    template <typename T>
    decltype(auto) minmax(T&& x, T&& y)

    if constexpr(std::is_lvalue_reference_v<decltype(x)>)
    return std::minmax(std::forward<T>(x), std::forward<T>(y));
    else
    auto const res = std::minmax(x, y);
    return std::make_pair(res.first, res.second);







    share|improve this answer




















    • Note that this will not work with mixed rvalue/lvalue arguments (template deduction for T will fail then). It's the same as for OP's solution, but he/she asked for a better alternative to std::minmax, so I would suppose he/she actually wants to support mixed-category arguments.
      – Daniel Langr
      13 mins ago











    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%2f52512574%2fc-different-minmax-implementation%23new-answer', 'question_page');

    );

    Post as a guest






























    3 Answers
    3






    active

    oldest

    votes








    3 Answers
    3






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes








    up vote
    2
    down vote













    One solution is when T is an r-value reference then copy it instead of returning an r-value reference:



    #include <utility>

    template<class T>
    std::pair<T, T> minmax(T&& a, T&& b)
    if(a < b)
    return a, b;
    return b, a;



    When the argument is an r-value reference T is deduced as a non-reference type:



    int main() 
    int a = 1;
    int const b = 2;
    minmax(1, 1); // std::pair<int, int>
    minmax(a, a); // std::pair<int&, int&>
    minmax(b, b); // std::pair<const int&, const int&>






    share|improve this answer






















    • Note that this will not work with mixed rvalue/lvalue arguments (template deduction for T will fail then). It's the same as for OP's solution, but he/she asked for a better alternative to std::minmax, so I would suppose he/she actually wants to support mixed-category arguments.
      – Daniel Langr
      12 mins ago










    • @DanielLangr It doesn't support mixed rvalue/lvalue arguments by design.
      – Maxim Egorushkin
      10 mins ago














    up vote
    2
    down vote













    One solution is when T is an r-value reference then copy it instead of returning an r-value reference:



    #include <utility>

    template<class T>
    std::pair<T, T> minmax(T&& a, T&& b)
    if(a < b)
    return a, b;
    return b, a;



    When the argument is an r-value reference T is deduced as a non-reference type:



    int main() 
    int a = 1;
    int const b = 2;
    minmax(1, 1); // std::pair<int, int>
    minmax(a, a); // std::pair<int&, int&>
    minmax(b, b); // std::pair<const int&, const int&>






    share|improve this answer






















    • Note that this will not work with mixed rvalue/lvalue arguments (template deduction for T will fail then). It's the same as for OP's solution, but he/she asked for a better alternative to std::minmax, so I would suppose he/she actually wants to support mixed-category arguments.
      – Daniel Langr
      12 mins ago










    • @DanielLangr It doesn't support mixed rvalue/lvalue arguments by design.
      – Maxim Egorushkin
      10 mins ago












    up vote
    2
    down vote










    up vote
    2
    down vote









    One solution is when T is an r-value reference then copy it instead of returning an r-value reference:



    #include <utility>

    template<class T>
    std::pair<T, T> minmax(T&& a, T&& b)
    if(a < b)
    return a, b;
    return b, a;



    When the argument is an r-value reference T is deduced as a non-reference type:



    int main() 
    int a = 1;
    int const b = 2;
    minmax(1, 1); // std::pair<int, int>
    minmax(a, a); // std::pair<int&, int&>
    minmax(b, b); // std::pair<const int&, const int&>






    share|improve this answer














    One solution is when T is an r-value reference then copy it instead of returning an r-value reference:



    #include <utility>

    template<class T>
    std::pair<T, T> minmax(T&& a, T&& b)
    if(a < b)
    return a, b;
    return b, a;



    When the argument is an r-value reference T is deduced as a non-reference type:



    int main() 
    int a = 1;
    int const b = 2;
    minmax(1, 1); // std::pair<int, int>
    minmax(a, a); // std::pair<int&, int&>
    minmax(b, b); // std::pair<const int&, const int&>







    share|improve this answer














    share|improve this answer



    share|improve this answer








    edited 1 hour ago

























    answered 2 hours ago









    Maxim Egorushkin

    80.6k1195175




    80.6k1195175











    • Note that this will not work with mixed rvalue/lvalue arguments (template deduction for T will fail then). It's the same as for OP's solution, but he/she asked for a better alternative to std::minmax, so I would suppose he/she actually wants to support mixed-category arguments.
      – Daniel Langr
      12 mins ago










    • @DanielLangr It doesn't support mixed rvalue/lvalue arguments by design.
      – Maxim Egorushkin
      10 mins ago
















    • Note that this will not work with mixed rvalue/lvalue arguments (template deduction for T will fail then). It's the same as for OP's solution, but he/she asked for a better alternative to std::minmax, so I would suppose he/she actually wants to support mixed-category arguments.
      – Daniel Langr
      12 mins ago










    • @DanielLangr It doesn't support mixed rvalue/lvalue arguments by design.
      – Maxim Egorushkin
      10 mins ago















    Note that this will not work with mixed rvalue/lvalue arguments (template deduction for T will fail then). It's the same as for OP's solution, but he/she asked for a better alternative to std::minmax, so I would suppose he/she actually wants to support mixed-category arguments.
    – Daniel Langr
    12 mins ago




    Note that this will not work with mixed rvalue/lvalue arguments (template deduction for T will fail then). It's the same as for OP's solution, but he/she asked for a better alternative to std::minmax, so I would suppose he/she actually wants to support mixed-category arguments.
    – Daniel Langr
    12 mins ago












    @DanielLangr It doesn't support mixed rvalue/lvalue arguments by design.
    – Maxim Egorushkin
    10 mins ago




    @DanielLangr It doesn't support mixed rvalue/lvalue arguments by design.
    – Maxim Egorushkin
    10 mins ago












    up vote
    1
    down vote













    I am not exactly sure what are you trying to achieve. You wrote:




    without any overhead




    but your solution will copy lvalue arguments. Is it what you want?



    Anyway, you cannot use two forwarding references with the same template parameter this way, since it will fail if both function arguments have different categories:



    template <typename T> void f(T&& a, T&& b) 

    int main()
    int a = 3;
    f(a, 1); // error: template argument deduction/substitution failed



    For the first function argument, T would be deduced as int&, and for second as int.




    If you want to remove any copying, the only possibility is the member of the resulting pair to be:



    1. a (const) lvalue reference to the corresponding function argument in case it is an lvalue,


    2. a value moved from that argument if is it an rvalue.


    I don't think this is possible to achieve. Consider:



    std::string a("hello");
    auto p = minmax(a, std::string("world"));


    Here the resulting type would be std::pair<std::string&, std::string>. However, in case of



    auto p = minmax(a, std::string("earth"));


    the resulting type would be different, namely std::pair<std::string, std::string&>.



    Therefore, the resulting type would depend on a runtime condition (which generally requires runtime polymorphism).




    UPDATE



    Out of curiosity, I just came up with a wrapper that can hold some object either by (const) reference or by value:



    template <typename T>
    class ref_val
    std::unique_ptr<T> val_;
    const T& ref_;
    public:
    ref_val() = delete;
    ref_val(const T& ref) : ref_(ref)
    ref_val(T&& val) : val_(new T(std::move(val))), ref_(*val_)
    ref_val(const ref_val&) = delete;
    ref_val(ref_val&& other) : val_(std::move(other.val_)), ref_(val_ ? *val_ : other.ref_)
    const T& get() const return ref_;
    ... // assignment operators, ...
    ;


    Note that it adds another overhead in the form of dynamic allocation. A solution based on std::aligned_storage and placement new would be more efficient.



    With that, you can define minmax as:



    template <typename T, typename U,
    typename V = std::enable_if_t<std::is_same_v<std::decay_t<T>, std::decay_t<U>>, std::decay_t<T>>>
    std::pair<ref_val<V>, ref_val<V>> minmax(T&& a, U&& b)
    if (b < a) return std::forward<U>(b), std::forward<T>(a) ;
    else return std::forward<T>(a), std::forward<U>(b) ;



    Live demo is here: https://wandbox.org/permlink/FBNjB3gxnYUmMSwR



    This is very basic implementation, but it should prevent copying both from lvalue and rvalue arguments of minmax.






    share|improve this answer


























      up vote
      1
      down vote













      I am not exactly sure what are you trying to achieve. You wrote:




      without any overhead




      but your solution will copy lvalue arguments. Is it what you want?



      Anyway, you cannot use two forwarding references with the same template parameter this way, since it will fail if both function arguments have different categories:



      template <typename T> void f(T&& a, T&& b) 

      int main()
      int a = 3;
      f(a, 1); // error: template argument deduction/substitution failed



      For the first function argument, T would be deduced as int&, and for second as int.




      If you want to remove any copying, the only possibility is the member of the resulting pair to be:



      1. a (const) lvalue reference to the corresponding function argument in case it is an lvalue,


      2. a value moved from that argument if is it an rvalue.


      I don't think this is possible to achieve. Consider:



      std::string a("hello");
      auto p = minmax(a, std::string("world"));


      Here the resulting type would be std::pair<std::string&, std::string>. However, in case of



      auto p = minmax(a, std::string("earth"));


      the resulting type would be different, namely std::pair<std::string, std::string&>.



      Therefore, the resulting type would depend on a runtime condition (which generally requires runtime polymorphism).




      UPDATE



      Out of curiosity, I just came up with a wrapper that can hold some object either by (const) reference or by value:



      template <typename T>
      class ref_val
      std::unique_ptr<T> val_;
      const T& ref_;
      public:
      ref_val() = delete;
      ref_val(const T& ref) : ref_(ref)
      ref_val(T&& val) : val_(new T(std::move(val))), ref_(*val_)
      ref_val(const ref_val&) = delete;
      ref_val(ref_val&& other) : val_(std::move(other.val_)), ref_(val_ ? *val_ : other.ref_)
      const T& get() const return ref_;
      ... // assignment operators, ...
      ;


      Note that it adds another overhead in the form of dynamic allocation. A solution based on std::aligned_storage and placement new would be more efficient.



      With that, you can define minmax as:



      template <typename T, typename U,
      typename V = std::enable_if_t<std::is_same_v<std::decay_t<T>, std::decay_t<U>>, std::decay_t<T>>>
      std::pair<ref_val<V>, ref_val<V>> minmax(T&& a, U&& b)
      if (b < a) return std::forward<U>(b), std::forward<T>(a) ;
      else return std::forward<T>(a), std::forward<U>(b) ;



      Live demo is here: https://wandbox.org/permlink/FBNjB3gxnYUmMSwR



      This is very basic implementation, but it should prevent copying both from lvalue and rvalue arguments of minmax.






      share|improve this answer
























        up vote
        1
        down vote










        up vote
        1
        down vote









        I am not exactly sure what are you trying to achieve. You wrote:




        without any overhead




        but your solution will copy lvalue arguments. Is it what you want?



        Anyway, you cannot use two forwarding references with the same template parameter this way, since it will fail if both function arguments have different categories:



        template <typename T> void f(T&& a, T&& b) 

        int main()
        int a = 3;
        f(a, 1); // error: template argument deduction/substitution failed



        For the first function argument, T would be deduced as int&, and for second as int.




        If you want to remove any copying, the only possibility is the member of the resulting pair to be:



        1. a (const) lvalue reference to the corresponding function argument in case it is an lvalue,


        2. a value moved from that argument if is it an rvalue.


        I don't think this is possible to achieve. Consider:



        std::string a("hello");
        auto p = minmax(a, std::string("world"));


        Here the resulting type would be std::pair<std::string&, std::string>. However, in case of



        auto p = minmax(a, std::string("earth"));


        the resulting type would be different, namely std::pair<std::string, std::string&>.



        Therefore, the resulting type would depend on a runtime condition (which generally requires runtime polymorphism).




        UPDATE



        Out of curiosity, I just came up with a wrapper that can hold some object either by (const) reference or by value:



        template <typename T>
        class ref_val
        std::unique_ptr<T> val_;
        const T& ref_;
        public:
        ref_val() = delete;
        ref_val(const T& ref) : ref_(ref)
        ref_val(T&& val) : val_(new T(std::move(val))), ref_(*val_)
        ref_val(const ref_val&) = delete;
        ref_val(ref_val&& other) : val_(std::move(other.val_)), ref_(val_ ? *val_ : other.ref_)
        const T& get() const return ref_;
        ... // assignment operators, ...
        ;


        Note that it adds another overhead in the form of dynamic allocation. A solution based on std::aligned_storage and placement new would be more efficient.



        With that, you can define minmax as:



        template <typename T, typename U,
        typename V = std::enable_if_t<std::is_same_v<std::decay_t<T>, std::decay_t<U>>, std::decay_t<T>>>
        std::pair<ref_val<V>, ref_val<V>> minmax(T&& a, U&& b)
        if (b < a) return std::forward<U>(b), std::forward<T>(a) ;
        else return std::forward<T>(a), std::forward<U>(b) ;



        Live demo is here: https://wandbox.org/permlink/FBNjB3gxnYUmMSwR



        This is very basic implementation, but it should prevent copying both from lvalue and rvalue arguments of minmax.






        share|improve this answer














        I am not exactly sure what are you trying to achieve. You wrote:




        without any overhead




        but your solution will copy lvalue arguments. Is it what you want?



        Anyway, you cannot use two forwarding references with the same template parameter this way, since it will fail if both function arguments have different categories:



        template <typename T> void f(T&& a, T&& b) 

        int main()
        int a = 3;
        f(a, 1); // error: template argument deduction/substitution failed



        For the first function argument, T would be deduced as int&, and for second as int.




        If you want to remove any copying, the only possibility is the member of the resulting pair to be:



        1. a (const) lvalue reference to the corresponding function argument in case it is an lvalue,


        2. a value moved from that argument if is it an rvalue.


        I don't think this is possible to achieve. Consider:



        std::string a("hello");
        auto p = minmax(a, std::string("world"));


        Here the resulting type would be std::pair<std::string&, std::string>. However, in case of



        auto p = minmax(a, std::string("earth"));


        the resulting type would be different, namely std::pair<std::string, std::string&>.



        Therefore, the resulting type would depend on a runtime condition (which generally requires runtime polymorphism).




        UPDATE



        Out of curiosity, I just came up with a wrapper that can hold some object either by (const) reference or by value:



        template <typename T>
        class ref_val
        std::unique_ptr<T> val_;
        const T& ref_;
        public:
        ref_val() = delete;
        ref_val(const T& ref) : ref_(ref)
        ref_val(T&& val) : val_(new T(std::move(val))), ref_(*val_)
        ref_val(const ref_val&) = delete;
        ref_val(ref_val&& other) : val_(std::move(other.val_)), ref_(val_ ? *val_ : other.ref_)
        const T& get() const return ref_;
        ... // assignment operators, ...
        ;


        Note that it adds another overhead in the form of dynamic allocation. A solution based on std::aligned_storage and placement new would be more efficient.



        With that, you can define minmax as:



        template <typename T, typename U,
        typename V = std::enable_if_t<std::is_same_v<std::decay_t<T>, std::decay_t<U>>, std::decay_t<T>>>
        std::pair<ref_val<V>, ref_val<V>> minmax(T&& a, U&& b)
        if (b < a) return std::forward<U>(b), std::forward<T>(a) ;
        else return std::forward<T>(a), std::forward<U>(b) ;



        Live demo is here: https://wandbox.org/permlink/FBNjB3gxnYUmMSwR



        This is very basic implementation, but it should prevent copying both from lvalue and rvalue arguments of minmax.







        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited 35 mins ago

























        answered 1 hour ago









        Daniel Langr

        5,1662039




        5,1662039




















            up vote
            1
            down vote













            With C++17 it is possible to use constexpr if to tie lvalue args and copy everything else. With C++11 I would probably think twice before building an angle brackets moster with a scary look for such a simple use case.



            godbolt, coliru



            template <typename T>
            decltype(auto) minmax(T&& x, T&& y)

            if constexpr(std::is_lvalue_reference_v<decltype(x)>)
            return std::minmax(std::forward<T>(x), std::forward<T>(y));
            else
            auto const res = std::minmax(x, y);
            return std::make_pair(res.first, res.second);







            share|improve this answer




















            • Note that this will not work with mixed rvalue/lvalue arguments (template deduction for T will fail then). It's the same as for OP's solution, but he/she asked for a better alternative to std::minmax, so I would suppose he/she actually wants to support mixed-category arguments.
              – Daniel Langr
              13 mins ago















            up vote
            1
            down vote













            With C++17 it is possible to use constexpr if to tie lvalue args and copy everything else. With C++11 I would probably think twice before building an angle brackets moster with a scary look for such a simple use case.



            godbolt, coliru



            template <typename T>
            decltype(auto) minmax(T&& x, T&& y)

            if constexpr(std::is_lvalue_reference_v<decltype(x)>)
            return std::minmax(std::forward<T>(x), std::forward<T>(y));
            else
            auto const res = std::minmax(x, y);
            return std::make_pair(res.first, res.second);







            share|improve this answer




















            • Note that this will not work with mixed rvalue/lvalue arguments (template deduction for T will fail then). It's the same as for OP's solution, but he/she asked for a better alternative to std::minmax, so I would suppose he/she actually wants to support mixed-category arguments.
              – Daniel Langr
              13 mins ago













            up vote
            1
            down vote










            up vote
            1
            down vote









            With C++17 it is possible to use constexpr if to tie lvalue args and copy everything else. With C++11 I would probably think twice before building an angle brackets moster with a scary look for such a simple use case.



            godbolt, coliru



            template <typename T>
            decltype(auto) minmax(T&& x, T&& y)

            if constexpr(std::is_lvalue_reference_v<decltype(x)>)
            return std::minmax(std::forward<T>(x), std::forward<T>(y));
            else
            auto const res = std::minmax(x, y);
            return std::make_pair(res.first, res.second);







            share|improve this answer












            With C++17 it is possible to use constexpr if to tie lvalue args and copy everything else. With C++11 I would probably think twice before building an angle brackets moster with a scary look for such a simple use case.



            godbolt, coliru



            template <typename T>
            decltype(auto) minmax(T&& x, T&& y)

            if constexpr(std::is_lvalue_reference_v<decltype(x)>)
            return std::minmax(std::forward<T>(x), std::forward<T>(y));
            else
            auto const res = std::minmax(x, y);
            return std::make_pair(res.first, res.second);








            share|improve this answer












            share|improve this answer



            share|improve this answer










            answered 20 mins ago









            bobah

            13k12246




            13k12246











            • Note that this will not work with mixed rvalue/lvalue arguments (template deduction for T will fail then). It's the same as for OP's solution, but he/she asked for a better alternative to std::minmax, so I would suppose he/she actually wants to support mixed-category arguments.
              – Daniel Langr
              13 mins ago

















            • Note that this will not work with mixed rvalue/lvalue arguments (template deduction for T will fail then). It's the same as for OP's solution, but he/she asked for a better alternative to std::minmax, so I would suppose he/she actually wants to support mixed-category arguments.
              – Daniel Langr
              13 mins ago
















            Note that this will not work with mixed rvalue/lvalue arguments (template deduction for T will fail then). It's the same as for OP's solution, but he/she asked for a better alternative to std::minmax, so I would suppose he/she actually wants to support mixed-category arguments.
            – Daniel Langr
            13 mins ago





            Note that this will not work with mixed rvalue/lvalue arguments (template deduction for T will fail then). It's the same as for OP's solution, but he/she asked for a better alternative to std::minmax, so I would suppose he/she actually wants to support mixed-category arguments.
            – Daniel Langr
            13 mins ago


















             

            draft saved


            draft discarded















































             


            draft saved


            draft discarded














            StackExchange.ready(
            function ()
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f52512574%2fc-different-minmax-implementation%23new-answer', 'question_page');

            );

            Post as a guest













































































            Comments

            Popular posts from this blog

            What does second last employer means? [closed]

            List of Gilmore Girls characters

            Confectionery