std::function const correctness not followed - c++

I was surprised to find this code compiles:
#include <functional>
struct Callable {
void operator() () { count++; }
void operator() () const = delete;
int count = 0;
};
int main() {
const Callable counter;
// counter(); //error: use of deleted function 'void Callable::operator()() const'
std::function<void(void)> f = counter;
f();
const auto cf = f;
cf();
}
https://wandbox.org/permlink/FH3PoiYewklxmiXl
This will call the non-const call operator of Callable. Comparatively, if you do const auto cf = counter; cf(); then it errors out as expected. So, why does const correctness not seem to be followed with std::function?

std::function adds a layer of indirection, and this layer of indirection does not pass through constness to the callable.
I'm not really sure why this is — probably because std::function takes a copy of the callable and has no need to keep the copy const (in fact this might break assignment semantics) — I'm also not really sure why you'd need it to.
(Of course, directly invoking operator() on an object of a type that you just so happened to call Callable and declared as const will require a const context, as it would with any other object.)
Best practice is to give the callable a const operator() and leave it at that.
tl;dr: true, but not a bug, and doesn't matter

You are correct to find this weird. The call operator of std::function is marked const but the constness is not propagated when the target object is actually invoked. The proposal p0045r1 seems to remedy this by making the call operator of std::function non-const but allowing the following syntax:
std::function<return_type(arg_type) const>

The reason is that assigning counter to std::function object creates a copy of counter.
In your case, f is initialized using the following constructor:
template< class F >
function( F f );
As described here, this constructor "initializes the target with std::move(f)" - a new object of type Callable is created and initialized using copy constructor.
If you'd like to initialize f with a reference to counter instead, you can use std::ref:
std::function<void()> f = std::ref(counter);
std::ref returns an instance of std::reference_wrapper, which has operator (), which calls Callable's operator () const. As expected, you'll get an error, since that operator is deleted.

Related

no matching function call when using lambda function as an argument of a template function

I defined a class that receives an lambda function through constructor. The code is as follows. Why did the definition of t0 pass compilation after using the std::forward, and t1 incur an error?
#include <iostream>
template <typename Func>
class Test {
public:
Test(Func &&func) : m_func(std::forward<Func &&>(func)) {}
void Run() { m_func(); }
private:
Func &&m_func;
};
template <typename Func>
class Foo {
public:
Foo(Func &func) : m_func(func) {}
void Run() { m_func(); }
private:
Func &m_func;
};
int main() {
const auto print = []() { std::cout << "Hello" << std::endl; };
using Print = decltype(print);
Test<decltype(print)> t0(std::forward<Print&&>(print));
t0.Run();
Test<void()> t1(Print{});
t1.Run();
Foo<decltype(print)> t3(std::forward<Print&&>(print));
t3.Run();
Foo<void()> t4(Print{});
t4.Run();
}
[Update]
The definition of t1 should be as following. thx for #JaMiT.
Test<void(*)()> t1([]() { std::cout << "Hello" << std::endl; });
But I'm still confused about the definition of t0. If I deletes the std::forward, it incurs a compilation error.
[Update]
It works if I change the definition of t0 to Test<void (*)()> t0(print);. What's the difference between Test<decltype(print)> t0(print); that causes a compilation error?
Why did the definition of t0 pass compilation after using the std::forward,
Because that is how you declared the constructor of Test. The constructor takes as its parameter an rvalue reference to the template parameter. You explicitly provided decltype(print) as the template argument, so the constructor takes an rvalue of that type. There will be no copying (no pass by value), and an lvalue reference will not cut it. You must provide an rvalue.
By adding std::forward<Print&&>, you converted print to an rvalue. (It would have been simpler to add std::move instead. Rule of thumb: use std::forward when dealing with a "placeholder" type, such as a template parameter, and use std::move when dealing with a fixed type.)
Caution: After using std::forward<Print&&>(print) or std::move(print), you should treat print as uninitialized. That is, your initialization of t3 is a potential bug.
Another tweak that would make this compile is to specify decltype(print)& (with the ampersand at the end) as the template argument. When Func is an lvalue reference, Func&& collapses to Func, which means the constructor would take an lvalue reference instead of an rvalue reference. (Reference collapsing is a key component of forwarding references, on which perhaps you based your code? However, forwarding references would require the constructor to itself be a template.)
and t1 incur an error?
For t1, you specified the template argument as void(), which is the type of a function. Lambdas are objects, not functions, so there is a type mismatch.
On the other hand, a lambda with no captures (nothing inside the []) can implicitly convert to a pointer to a function. This is a place where confusion lurks, because functions also decay to pointers so people can get used to interchanging function types and pointer to function types. To specify a pointer to a function, use void(*)() instead of void().
Caution: Implicit conversions can wreak havoc when combined with references. Then again, you were already in the danger zone when you combined temporary objects (Print{}) with references. Your code would be safer if you changed the data member to Func m_func;. In addition to avoiding dangling references, this would be more efficient (less indirection) when storing a pointer-to-function.
template <typename Func>
class Test {
public:
// Constructor can still take a reference and forward it to the member.
Test(Func &&func) : m_func(std::forward<Func &&>(func)) {}
void Run() { m_func(); }
private:
Func m_func; // Not necessarily a reference
};
There are still potential issues (e.g. Func could be specified as a reference type), but at least this is safer. I choose to treat the remaining issues as out-of-scope for this question about syntax.
It works if I change the definition of t0 to Test<void (*)()> t0(print);.
This combines some concepts I presented earlier. The template argument is now a pointer to a function, so your lambda (print) will undergo an implicit conversion, similar to the t1 case. The result of an implicit conversion is an rvalue, which is what your constructor expects (no need to forward or move).
Caution: By "works", you really mean "compiles". The fact that you asked this question suggests you already know the following, but for the benefit of others: getting code to compile is a necessary step, but that by itself does not mean the code is correct and works as intended. Don't be satisfied when a tweak you do not understand makes your code compile – ask questions!

smart pointer to const in function's signature

I was wondering if the implicit cast I have when passing a shared_ptr < T> as argument to a function taking shared_ptr < const T> involves some hidden costs (such as the construction of an extra copy).
void f(std::shared_ptr<const Widget> ){}
int main(){
std::shared_ptr<Widget> p;
f(p);
return 0;
}
I'm assuming that in both cases I am paying for the refcount increment and decrement.
Moreover I wonder why the code doesn't compile if I define the function f() with the following signature:
void f(shared_ptr<const Widget>& ){}
What wonders me more is the fact that this does:
void f(const shared_ptr<const Widget>& ){}
Why does pass by value work?
Your code works because of the smart_ptr constructor overload (9):
template< class Y >
shared_ptr( const shared_ptr<Y>& r ) noexcept;
Constructs a shared_ptr which shares ownership of the object managed
by r. If r manages no object, this manages no object too. The
template overload doesn't participate in overload resolution if Y is
not implicitly convertible to (until C++17)compatible with (since
C++17) T*.
Why does it not compile when the method expects a shared_ptr<const Widget>&?
If you change the signature to
void f(shared_ptr<const Widget>& ){}
You cannot have the conversion and passing to the method in one step anymore, because a temporary (the one resulting from the conversion) cannot bind to a non-const reference. However, you can still do it in two steps:
int main(){
std::shared_ptr<Widget> p;
std::shared_ptr<const Widget> p2{p};
// f(p); // error: cannot bind non-const reference to temporary
f(p2); // OK
return 0;
}
Is there some overhead?
Concerning overhead: Yes there is a smart_ptr<const Widget> being contructed and then passed to the method (like it is shown explicitly in the above snippet).
Why does it again work when the method expects a const shared_ptr<const Widget>&?
Concerning your edit, why does it work again if you change the signature to this:
void f(const shared_ptr<const Widget>& ){}
In that case if you pass a shared_ptr<Widget>, there is still a converison taking place. However, now the temporary resulting from the conversion is allowed to bind to a const reference. Anyhow the method is not allowed to modify it, so there is no danger in allowing to pass a temporary.
Just another example
Note that temporaries not binding to non-const references is a rare case of C++ helping you to avoid stupid mistakes. Consider this:
void foo(int& x) { x += 2; }
int bar() { return 3; }
int main() { foo(bar()); } // error !
It just doesnt make much sense to pass a r-value to a function that expects a non-const l-value reference. You'd have no way to observe the changes made by foo on the value returned by bar.
Passing smartpointers # cpp core guidelines
Concerning passing smartpointers to funcions, note that the cpp coreguidelines has some items on that. The bottomline is: If the method is not participating in reference counting (probably the most common case) then dont pass a smartpointer but a raw pointer.

Why it does not declare a reference type if 'auto' var is initialized using a function returning reference?

When an 'auto' var is initialized using a function that returns a reference, why the var type is not a reference?
e.g. In following example, why type of x is Foo and not Foo& ?
class TestClass {
public:
Foo& GetFoo() { return mFoo; }
private:
Foo mFoo;
};
int main()
{
TestClass testClass;
auto x = testClass.GetFoo(); // Why type of x is 'Foo' and not 'Foo&' ?
return 0;
}
EDIT: The link explains how to get the reference, but my question is the reason for this behavior.
Because it would be annoying if it worked that way. How, for example, would you specify that you didn't want a reference?
When you use auto, you need to put const, &, &&, and volatile in yourself.
auto& x = testClass.GetFoo();
is your fix.
C++11 auto type inference rules drop reference, const and volatile qualifiers.
However, you may ask C++ compiler to use decltype type inference rules to keep all these qualifiers for declaring variable type. In your case it may be:
decltype(auto) x = testClass.GetFoo();
But this code can cause some side effects like reference to destroyed object, so you need to keep in mind the real variable type and life time.

Pass by value or rvalue-ref

For move enabled classes is there a difference between this two?
struct Foo {
typedef std::vector<std::string> Vectype;
Vectype m_vec;
//this or
void bar(Vectype&& vec)
{
m_vec = std::move(vec);
}
//that
void bar(Vectype vec)
{
m_vec = std::move(vec);
}
};
int main()
{
Vectype myvec{"alpha","beta","gamma"};
Foo fool;
fool.bar(std::move(myvec));
}
My understanding is that if you use a lvalue myvec you also required to introduce const
Vectype& version of Foo::bar() since Vectype&& won't bind. That's aside, in the rvalue case, Foo::bar(Vectype) will construct the vector using the move constructor or better yet elide the copy all together seeing vec is an rvalue (would it?). So is there a compelling reason to not to prefer by value declaration instead of lvalue and rvalue overloads?
(Consider I need to copy the vector to the member variable in any case.)
The pass-by-value version allows an lvalue argument and makes a copy of it. The rvalue-reference version can't be called with an lvalue argument.
Use const Type& when you don't need to change or copy the argument at all, use pass-by-value when you want a modifiable value but don't care how you get it, and use Type& and Type&& overloads when you want something slightly different to happen depending on the context.
The pass-by-value function is sufficient (and equivalent), as long as the argument type has an efficient move constructor, which is true in this case for std::vector.
Otherwise, using the pass-by-value function may introduce an extra copy-construction compared to using the pass-by-rvalue-ref function.
See the answer https://stackoverflow.com/a/7587151/1190077 to the related question Do I need to overload methods accepting const lvalue reference for rvalue references explicitly? .
Yes, the first one (Vectype&& vec) won't accept a const object or simply lvalue.
If you want to save the object inside like you do, it's best to copy(or move if you pass an rvalue) in the interface and then move, just like you did in your second example.

why must you provide the keyword const in operator overloads

Just curious on why a param has to be a const in operation overloading
CVector& CVector::operator= (const CVector& param)
{
x=param.x;
y=param.y;
return *this;
}
couldn't you have easily done something like this ??
CVector& CVector::operator= (CVector& param) //no const
{
x=param.x;
y=param.y;
return *this;
}
Isn't when something becomes a const, it is unchangeable for the remainder of the applications life ?? How does this differ in operation overloading ???
You don't need const:
#numerical25: Just curious on why a param has to be a const in operation overloading
It's not required, but it is a good design decision.
See the C++ standard Section 12.8-9:
A user-declared copy assignment
operator X::operator= is a non-static
non-template member function of class
X with exactly one parameter of type
X, X&, const X&, volatile X& or const
volatile X&
I think it's a good idea though:
Using a const parameter does seems like a logical design decision to me though because you want to ensure that the other value will not be changed.
It tells other people that use your class that you will not be changing the other value when you say something like: myObject = other; and it enforces this so you can't accidentally change other.
Also if you allowed non const references to the object as the parameter, then you are limiting the amount of objects that can use your function. If it is const it can be used for parameters that are const and non const. If your parameter is non const it can only be used by parameters that are non const.
const only applies to the current reference, not the object:
#numerical25: Isn't when something becomes a const, it is unchangeable for the remainder of the applications life ?? How does this differ in operation overloading ???
A const reference is simply that a reference that is const. It does not change the const-ness of the actual object you are passing in.
An example of non-const operator overloading:
Here is an example of operator overloading where the parameter is not const.
I DO NOT RECOMMEND TO DO THIS THOUGH:
class B
{
public:
const B& operator=(B& other)
{
other.x = 3;
x = other.x;
return *this;
}
int x;
};
void main(int argc, char** argv[])
{
B a;
a.x = 33;
B b;
b.x = 44;
a = b;//both a and b will be changed
return 0;
}
A const parameter is const throughout the function using it, it does not change its constness outside of it.
In this case you want to declare a const argument so that your assignment operator accepts both non-const variables and const variables; the latter case, in particular, includes the result of expressions, which is a temporary const variable and which you generally want to support in assignments.
If you used
CVector& CVector::operator= (CVector& param) // no const
then did this:
const CVector& my_vector = GetMyVector();
some_other_vector = my_vector; // call assignment operator - error!
You'll get an error because my_vector is a const CVector& and that can't be cast to a CVector& (non-const reference). It's just the local reference to it inside the operator= function that is const, not the entire object itself.
You can use the non-const variety, but this has two repercussions, one which is functional, and one which is about what you, as the writer of the function, are telling the user.
1) people calling the function that takes a non-const reference would not be able to call it using a const variable
2) when you have a function argument that's a non-const reference, you're signalling, "I reserver the right to change this". Typically, when a user of your function writes a = b;, he doesn't expect b to change.
Note that there's a third option you could use for this, pass-by-value:
CVector& CVector::operator= (CVector param) //no reference
This doesn't have either of the problems I mention above. However, it's very inefficient. Because of these three factors, passing by reference-to-const is preferred, especially in cases like a vector where copying can be expensive.
For the same reason you would use const anywhere: to ensure that future changes to the method don't inadvertently modify the passed in parameter, to help document the interface to notify callers that it is safe to pass param without risk of it changing, and to allow callers to pass in references that are declared as const in the calling code.
Another reason is to allow for conversions. For example:
string s = "foo";
s = "bar";
Here, an implementation might choose to only provide the assignment operator that takes a const reference to a string as a parameter, and depend on the compiler using a constructor to create a temporary string from the char * "bar". This would not work if the op='s parameter was not const, as you cannot bind a temporary to a non-const reference.
The const qualifier makes the passed parameter (in your example it is 'const CVector& param') as read only. The const qualifier ensures that the parameter (param) is not altered inside the operator=() method.
Without the const qualifier, the following is possible:
CVector& CVector::operator= (CVector& param)
{
x=param.x;
y=param.y;
param.x = 10; // some random value
param.y = 100;
return *this;
}
The above method alters the right hand side operand 'param' after assigning the value to the left hand side operand. The const qualifier helps you not to violate the semantics of the assignment operation.