C++ passing by const ref vs universal ref - c++

So I've recently learned about universal references and reference collapsing.
So let's say I have two different implementations of a max function like such.
template<class T>
T&& max(T&& a, T&& b)
{
return (a < b) ? b : a;
}
template<class T>
const T& max(const T& a, const T& b)
{
return (a < b) ? b : a;
}
One version takes its arguments by const reference and other takes them by universal reference.
What confuses me about these is when should either be used. Passing by const is mainly used so you can bind temporaries to it.
In the stl there's different uses as well. std::move takes universal reference where std::max takes by const ref.
What's the situations on where either should be used??
Say if I wanted to avoid the copy when it returns or keep a reference on return. Would it makes sense to have a const and nonconst version of the function etc

The first one should probably be:
// amended first version
template<class T>
decltype(auto) max(T&& a, T&& b) {
return (a < b) ? std::forward<T>(b) : std::forward<T>(a);
}
Otherwise it wouldn't work for temporaries (the original first option fails with two temporaries).
But even now, with the new version above, it cannot work for a mix of an rvalue and an lvalue:
int i = my_max(3, 5); // ok
i = my_max(i, 15); // fails
// no known conversion from 'int' to 'int &&' for 1st argument
To add to that, the return value of the first option is either an lvalue-ref or an rvalue-ref (with or without const, depending on the arguments). Getting back rvalue-ref to a temporary would not be the best idea, it would work if we immediately copy from it, but then it would be better to return byvalue, which is not the approach taken by the first version.
The pitfall with the second, the const-lvalue-ref version, is that if a temporary is sent to it, we return a const-lvalue-ref to a temporary which is bug prone (well, not more than returning an rvalue-ref to a temporary, but still). If you copy it immediately you are fine, if you take it by-ref you are in the UB zone:
// second version
template<class T>
const T& max(const T& a, const T& b) {
return (a < b) ? b : a;
}
std::string themax1 = max("hello"s, "world"s); // ok
const std::string& themax2 = max("hello"s, "world"s); // dangling ref
To solve the above problem, with the cost of redundant copying for the lvalue-ref case, we can have another option, returning byvalue:
// third version
template<class T1, class T2>
auto max(T1&& a, T2&& b) {
return (a < b) ? std::forward<T2>(b) : std::forward<T1>(a);
}
The language itself took the 2nd option for std::max, i.e. getting and returning const-ref. And the user shall be careful enough not to take a reference to temporaries.
Another option might be to support both rvalue and lvalue in their own semantic, with two overloaded functions:
template<class T1, class T2>
auto my_max(T1&& a, T2&& b) {
return (a < b) ? std::forward<T2>(b) : std::forward<T1>(a);
}
template<class T>
const T& my_max(const T& a, const T& b) {
return (a < b) ? b : a;
}
With this approach, you can always get the result as a const-ref: if you went to the first one you get back a value and extend its lifetime, if you went to the second one you bind a const-ref to a const-ref:
int i = my_max(3, 5); // first, copying
const int& i2 = my_max(i, 25); // first, life time is extended
const std::string& s = my_max("hi"s, "hello"s); // first, life time is extended
const std::string s2 = my_max("hi"s, "hello"s); // first, copying
const std::string& s3 = my_max(s, s2); // second, actual ref, no life time extension
The problem with the above suggestion is that it only takes you to the lvalue-ref version if both arguments are const lvalue-ref. If you want to cover all cases you will have to actually cover them all, as in the code below:
// handle rvalue-refs
template<class T1, class T2>
auto my_max(T1&& a, T2&& b) {
return (a < b) ? std::forward<T2>(b) : std::forward<T1>(a);
}
// handle all lvalue-ref combinations
template<class T>
const T& my_max(const T& a, const T& b) {
return (a < b) ? b : a;
}
template<class T>
const T& my_max(T& a, const T& b) {
return (a < b) ? b : a;
}
template<class T>
const T& my_max(const T& a, T& b) {
return (a < b) ? b : a;
}
template<class T>
const T& my_max(T& a, T& b) {
return (a < b) ? b : a;
}
A last approach to achieve the overloading, and supporting all kind of lvalue-ref (const and non-const, including a mixture), without the need for implementing the 4 combinations for lvalue-ref, would be based on SFINAE (here presented with C++20 with a constraint, using requires):
template<class T1, class T2>
auto my_max(T1&& a, T2&& b) {
return (a < b) ? std::forward<T2>(b) : std::forward<T1>(a);
}
template<class T1, class T2>
requires std::is_lvalue_reference_v<T1> &&
std::is_lvalue_reference_v<T2> &&
std::is_same_v<std::remove_cvref_t<T1>, std::remove_cvref_t<T2>>
auto& my_max(T1&& a, T2&& b) {
return (a < b) ? b : a;
}
And if you bear with me for one past the last... (hope you are not in a bucket for monsieur state by now). We can achieve it all in one function!, and as a side benefit, even allow cases of comparison between derived and base if supported, so the following would work fine:
A a = 1;
B b = 2; // B is derived from A
// if comparison between A and B is supported, you can do
const A& max1 = my_max(a, b); // const-ref to b
const A& max2 = my_max(a, B{-1}); // const-ref to a temporary copied from a
const A& max3 = my_max(a, B{3}); // const-ref to B{3}
This would be the code to support this with a single function:
template<typename T1, typename T2>
struct common_return {
using type = std::common_reference_t<T1, T2>;
};
template<typename T1, typename T2>
requires std::is_lvalue_reference_v<T1> &&
std::is_lvalue_reference_v<T2> &&
has_common_base<T1, T2> // see code in link below
struct common_return<T1, T2> {
using type = const std::common_reference_t<T1, T2>&;
};
template<typename T1, typename T2>
using common_return_t = typename common_return<T1, T2>::type;
template<class T1, class T2>
common_return_t<T1, T2> my_max(T1&& a, T2&& b)
{
if(a < b) {
return std::forward<T2>(b);
}
return std::forward<T1>(a);
}
The machinery for the above can be found here.
For the variadic version of this, you can follow this SO post.

Related

C++ overloading operator, constant parameter or pass by value?

template <typename T>
T operator+(T a, const T& b) {
a += b;
return a;
}
template <typename T>
T operator+(const T& a, const T& b) {
T tmp {a};
tmp += b;
return tmp;
}
Is there any reason why you would pass argument as constant reference like the second function, over directly passing by value like the first function, since you need a temporary variable anyway?
Edit 1:
I think I should mention that these 2 functions alternative are just for handling case with lvalue arguments, and that I am to provide 2 other functions for handling with rvalue arguments, as follows.
template <typename T>
T operator+(T&& a, const T& b) {
a += b;
return std::move(a);
}
template <typename T>
T operator+(const T& a, T&& b) {
b += a;
return std::move(b);
}
So the emphasis of the question is, why would I need to explicitly create a temporary variable (function 2), when I can just let the language facilitate that automatically for me (function 1)?
template <typename T>
T operator+(T a, const T& b) {
a += b;
return a;
}
In here you are making a copy of variable a which is passed in here then you are updating a copy. which requires three copies to be created, and again you are returning by value.
template <typename T>
T operator+(const T& a, const T& b) {
T tmp {a};
tmp += b;
return tmp;
}
in here your tmp variable has a local scope and variable a is const reference so no modification to the value of a is allowed.
And you are returning a copy of temp which is a local variable.
Both work fine but the difference is in the number of copies created. you are making more copies in 1 st case than of the second.
Although the second one will be optimized for tmp variable to use move semantics in order to make fewer copies. so you will have faster performance in 2nd case

How to construct an object either from a const reference or temporary via forwarding template

Consider this minimal example
template <class T>
class Foo
{
public:
Foo(const T& t_)
: t(t_)
{
}
Foo(T&& t_)
: t(std::move(t_))
{
}
T t;
};
template <typename F>
Foo<F> makeFoo(F&& f)
{
return Foo<F>(std::forward<F>(f));
}
int main()
{
class C
{
};
C c;
makeFoo(c);
}
MSVC 2017 fails with a redefinition error of Foo's ctor. Apparently T gets deduced to C& instead of the intended C. How exactly does that happen and how to modify the code so that it does what is inteded: either copy construct Foo::t from a const reference or move construct it from an r-value.
In C++17 you can simply write:
template <typename F>
auto makeFoo(F&& f)
{
return Foo(std::forward<F>(f));
}
because of class template argument deduction.
In C++14 you can write:
template <typename F>
auto makeFoo(F&& f)
{
return Foo<std::decay_t<F>>(std::forward<F>(f));
}
template <class F, class R = std::decay_t<F>>
Foo<R> makeFoo(F&& f)
{
return Foo<R>(std::forward<F>(f));
}
that is a clean and simple way to solve your problem.
Decay is an appropriate way to convert a type into a type suitable for storing somewhere. It does bad things with array types but otherwise does pretty much the right thing; your code doesn't work with array types anyhow.
The compiler error is due to reference collapsing rules.
X X& X const& X&&
int int& int const& int&&
int& int& int& int&
int const int const& int const& int const&&
int&& int& int& int&&
int const& int const& int const& int const&
these may seem strange.
The first rule is that a const reference is a reference, but a reference to const is different. You cannot qualify the "reference" part; you can only const-qualify the referred part.
When you have T=int&, when you calculate T const or const T, you just get int&.
The second part has to do with how using r and l value references together work. When you do int& && or int&& & (which you cannot do directly; instead you do T=int& then T&& or T=int&& and T&), you always get an lvalue reference -- T&. lvalue wins out over rvalue.
Then we add in the rules for how T&& types are deduced; if you pass a mutable lvalue of type C, you get T=C& in the call to makeFoo.
So you had:
template<F = C&>
Foo<C&> makeFoo( C& && f )
as your signature, aka
template<F = C&>
Foo<C&> makeFoo( C& f )
now we examine Foo<C&>. It has two ctors:
Foo( C& const& )
Foo( C& && )
for the first one, const on a reference is discarded:
Foo( C& & )
Foo( C& && )
next, a reference to a reference is a reference, and lvalue references win out over rvalue references:
Foo( C& )
Foo( C& )
and there we go, two identical signature constructors.
TL;DR -- do the thing at the start of this answer.
Issue is that typename provided to class is reference in one case:
template <typename F>
Foo<F> makeFoo(F&& f)
{
return Foo<F>(std::forward<F>(f));
}
becomes
template <>
Foo<C&> makeFoo(C& f)
{
return Foo<C&>(std::forward<C&>(f));
}
You probably want some decay:
template <typename F>
Foo<std::decay_t<F>> makeFoo(F&& f)
{
return Foo<std::decay_t<F>>(std::forward<F>(f));
}
This happens because of reference collapsing.
The F&& in your code is a forwarding reference, which means it can be either an lvalue reference or an rvalue reference depending on the type of the argument to which it binds.
In your case, if F&& binds to an argument of type C&& (an rvalue reference to C), F is simply deduced as C. However, if F&& binds to an argument of type C& (as in your example), the reference collapsing rules determine the type deduced for F:
T& & -> T&
T& && -> T&
T&& & -> T&
T&& && -> T&&
Thus, F is deduced as C&, since C& && collapses to C&.
You can use remove_reference to remove any reference from the deduced type:
remove_reference_t<C> -> C
remove_reference_t<C&> -> C
You will probably also want to use remove_cv to remove any potential const (or volatile) qualifier:
remove_cv_t<remove_reference_t<C>> -> C
remove_cv_t<remove_reference_t<C&>> -> C
remove_cv_t<remove_reference_t<C const>> -> C
remove_cv_t<remove_reference_t<C const&>> -> C
In C++20, there is a combined remove_cvref trait which can save some typing. However, many implementations just use decay, which does the same thing, but also turns array and function types into pointers, which may or may not be desirable depending on your use case (some parts of the standard library have switched from using decay to using remove_cvref in C++20).

Is this max function decent?

I am trying to understand new c++ features and I came up with my implementation of max function:
#include <utility>
template <typename T>
concept bool Comparable() {
return requires(T a, T b)
{
{a > b} -> bool;
{a < b} -> bool;
{a >= b} -> bool;
{a <= b} -> bool;
};
}
template<Comparable T, Comparable U>
constexpr decltype(auto) max(T&& a, U&& b)
{
return std::forward<T>(a) > std::forward<U>(b) ? std::forward<T>(a) : std::forward<U>(b);
}
int main()
{
constexpr int a = 2, b = 3;
return max(a, b);
}
Can this replace the macro version?
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
And if not what can be improved?
I am compiling with gcc-8.1.0 and -std=c++2a, -fconcepts and -O3 flags
EDIT:
Thank you for your suggestions, this is how it looks now:
#include <utility>
template<typename T, typename U>
concept bool TypesLessComparable() {
return requires(T a, U b)
{
{a < b} -> bool;
};
}
template<typename T, typename U>
constexpr decltype(auto) max(T&& a, U&& b) requires (TypesLessComparable<T, U>() == true)
{
return a < b ? std::forward<U>(b) : std::forward<T>(a);
}
int main()
{
constexpr int a = 2, b = 3;
return max(a, b);
}
std::forward<T>(a) > std::forward<U>(b) ? std::forward<T>(a) : std::forward<U>(b);
You forward the result twice. This is wrong for most types (only OK for types that implement move as a deep copy). You should remove forward from the comparison.
Is this max function decent?
No, there are at least 3 issues:
1 Unclear why you request multiple comparison operations when your function only requires one of them (it should be fine for a class only to define operator> when only max() is used)
2 Concept Comparable requires that type has comparison operations on itself, you may compare 2 object of different types. This invalidates usage of concepts (a function should use only what required/provided by concepts)
3 You may return objects of different types as well, one may convert to another but you should cover that by another concept.
Here is code example for case 2 and 3:
struct A { friend bool operator>( const A&, const A& ); };
struct B { friend bool operator>( const B&, const B& ); };
auto x = max( A(), B() );
To summarize:
Your function should require minimum set of operations on type(s), necessary for it to operate.
If type(s) passed concepts validation your function should compile successfully.
This is most probably not full set of requirements.

How to create a generic function for combining binary predicates?

There are several related questions, e.g. algorithms with multiple (unary) predicates, or multiple predicates in general. But they do not seem to tackle the generic case that I'm aiming at, and the answers are often out-dated in that they refer to constructs that are deprecated in C++11. It might all boil down to this question related to type deduction in lambdas, but I'm not sure whether it can be considered as a duplicate.
I tried to create a function that combines two arbitrary binary predicates (in the meaning of "types that implement a two-argument operator() and return a bool"). My goal was to have the possibility to write code like this:
auto p0 = somePredicate();
auto p1 = someOtherPredicate();
auto p2 = evenMorePredicates();
auto combined = and(p0, or(p1, p2));
I know that something similar can be achieved with lambdas (as also suggested in the answer to one of the questions linked above), but this requires the argument types to be repeated in the lambda itself. I'd like to know how such an and or or function could really be implemented generically - particularly, for binary predicates with arbitrary argument types.
My basic approach (and a suggestive example) is the following:
#include <functional>
#include <iostream>
template <typename A, typename B, typename P0, typename P1>
std::function<bool(const A&, const B&)> and(
const P0& p0, const P1& p1)
{
return [p0, p1](const A& a, const B& b)
{
return p0(a, b) && p1(a, b);
};
}
int main(int argc, char* argv[])
{
auto p0 = [](const int& a, const int& b)
{
return a < 0 && b > 0;
};
auto p1 = [](const int& a, const int& b)
{
return a < 1 && b > 4;
};
// This is what I want:
//auto combined = and(p0, p1);
// This works, but ...
auto combined = and<int, int, std::function<bool(const int&, const int&)>, std::function<bool(const int&, const int&)>>(p0, p1);
std::cout << "combined(-3,7) : " << combined(-3, 7) << std::endl;
std::cout << "combined(-3,1) : " << combined(-3, 1) << std::endl;
return 0;
}
In any case, the problem seems to be that the template parameters of the argument types can not be deduced at the call site. I tried variations thereof, based on other related questions here on stackoverflow, but no avail. My gut feeling is that there must be a (simple) solution for this, sneaking around some limitation of the type deduction system of lambdas, but I obviously didn't find the right path yet.
You could write a functor class to carry your expression:
template <typename P0, typename P1>
class AndFunctor {
public:
AndFunctor (P0 p0, P1 p1)
: m_p0{std::move(p0)}, m_p1{p1}
{}
template <typename T, typename U>
bool operator() (T&& t, U&& u) {
return m_p0(t, u) && m_p1(t, u);
}
private:
P0 m_p0;
P1 m_p1;
};
Then return an instance of this in your and function (and is a keyword, so this code renames it to and_p
template <typename P0, typename P1>
AndFunctor<P0,P1> and_p(P0 p0, P1 p1)
{
return { std::move(p0), std::move(p1) };
}
Then you just use it as you envisaged:
auto combined = and_p(p0, p1);
Live Demo

Returning value with the same constness as the parameter

I have a function with these overloads:
A& f(B& b)
{
return b.a;
}
const A& f(const B& b)
{
return b.a;
}
The return statement can be a much more complicated logic which returns some value that is const if b is const. For example, B is a container of containers (possibly with even deeper nesting) and we are searching for one element. In this case, copying the function is not a good idea. Is there any alternatives for achieving the same result?
I can think of some solutions, but I don't really like them.
template <typename T>
auto f(T& b) -> decltype(b.a)
{
return b.a;
}
It works but becomes complicated if that b.a is not so trivial (as in the with in the containers).
A& f(B& b)
{
return b.a;
}
const A& f(const B& b)
{
return f(const_cast<B&>(b));
}
This also works but if feels like a hack. Is there a simple and clean solution?
I would implement non-const version in terms of const version, not the other way round (as you did).
A& f(B& b)
{
return const_cast<A&>(f(static_cast<B const&>(b)));
}
It looks a bit safer from mutability point of view.
As for the template version, how about this:
template <typename T>
auto f(T& b) -> std::conditional_t<std::is_const<T>{}, A const&, A&>
{
return b.a;
}
If std::conditional_t (C++14) is not supported, then use std::conditional:
typename std::conditional<std::is_const<T>{}, A const&, A&>::type
Hope that helps.
A possible solution:
template<typename B>
struct Return_type { typedef A &Type; };
template<typename B>
struct Return_type<B const &> { typedef A const &Type; };
template<typename B>
typename Return_type<B>::Type f(B &&b)
{
return b.a;
}
EDIT:
C++14 will have function return type deduction, so this should work:
template<typename T>
auto &f(T &&b)
{
return b.a;
}
gcc 4.8.1 already compiles it.
If you know that f doesn't mutate the data, then the const_cast is not a hack, but rather a perfectly legitimate solution.
The typical example for this is strchr, which finds a character in a string. We know that this doesn't mutate, but it's perfectly sensible to allow a mutable string to be mutated by offering a non-const overload. However, the non-const version can happily be implemented in terms of the const overload.
Bear in mind that it's not a violation to use const_cast. It's only a violation to mutate a constant object.