C++ different minmax implementation
Clash 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
c++ c++11 stl
 |Â
show 10 more comments
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
c++ c++11 stl
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 comparingminmax
forint
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 (yesstd::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 resultingpair
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
 |Â
show 10 more comments
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
c++ c++11 stl
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
c++ c++11 stl
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 comparingminmax
forint
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 (yesstd::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 resultingpair
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
 |Â
show 10 more comments
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 comparingminmax
forint
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 (yesstd::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 resultingpair
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
 |Â
show 10 more comments
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&>
Note that this will not work with mixed rvalue/lvalue arguments (template deduction forT
will fail then). It's the same as for OP's solution, but he/she asked for a better alternative tostd::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
add a comment |Â
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:
a (const) lvalue reference to the corresponding function argument in case it is an lvalue,
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
.
add a comment |Â
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);
Note that this will not work with mixed rvalue/lvalue arguments (template deduction forT
will fail then). It's the same as for OP's solution, but he/she asked for a better alternative tostd::minmax
, so I would suppose he/she actually wants to support mixed-category arguments.
– Daniel Langr
13 mins ago
add a comment |Â
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&>
Note that this will not work with mixed rvalue/lvalue arguments (template deduction forT
will fail then). It's the same as for OP's solution, but he/she asked for a better alternative tostd::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
add a comment |Â
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&>
Note that this will not work with mixed rvalue/lvalue arguments (template deduction forT
will fail then). It's the same as for OP's solution, but he/she asked for a better alternative tostd::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
add a comment |Â
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&>
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&>
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 forT
will fail then). It's the same as for OP's solution, but he/she asked for a better alternative tostd::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
add a comment |Â
Note that this will not work with mixed rvalue/lvalue arguments (template deduction forT
will fail then). It's the same as for OP's solution, but he/she asked for a better alternative tostd::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
add a comment |Â
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:
a (const) lvalue reference to the corresponding function argument in case it is an lvalue,
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
.
add a comment |Â
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:
a (const) lvalue reference to the corresponding function argument in case it is an lvalue,
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
.
add a comment |Â
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:
a (const) lvalue reference to the corresponding function argument in case it is an lvalue,
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
.
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:
a (const) lvalue reference to the corresponding function argument in case it is an lvalue,
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
.
edited 35 mins ago
answered 1 hour ago
Daniel Langr
5,1662039
5,1662039
add a comment |Â
add a comment |Â
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);
Note that this will not work with mixed rvalue/lvalue arguments (template deduction forT
will fail then). It's the same as for OP's solution, but he/she asked for a better alternative tostd::minmax
, so I would suppose he/she actually wants to support mixed-category arguments.
– Daniel Langr
13 mins ago
add a comment |Â
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);
Note that this will not work with mixed rvalue/lvalue arguments (template deduction forT
will fail then). It's the same as for OP's solution, but he/she asked for a better alternative tostd::minmax
, so I would suppose he/she actually wants to support mixed-category arguments.
– Daniel Langr
13 mins ago
add a comment |Â
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);
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);
answered 20 mins ago
bobah
13k12246
13k12246
Note that this will not work with mixed rvalue/lvalue arguments (template deduction forT
will fail then). It's the same as for OP's solution, but he/she asked for a better alternative tostd::minmax
, so I would suppose he/she actually wants to support mixed-category arguments.
– Daniel Langr
13 mins ago
add a comment |Â
Note that this will not work with mixed rvalue/lvalue arguments (template deduction forT
will fail then). It's the same as for OP's solution, but he/she asked for a better alternative tostd::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
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
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
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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
forint
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