Reputation: 2537
Suppose we have an Object obj of type myType, and we would like to pass it to function Foo, which returns us some valuable information about obj. function Bar is where obj is declared and from which Foo is being called like this:
void Bar ()
{
myType obj; //default constructor
string valuableInfo = Foo(obj);
//do other stuff
//...
} //end of Bar()
This snippet of code of course does not say much about whether Foo takes obj as a reference or as value, and whether or not Foo modifies obj in any way.
of course if Foo takes obj as value or const reference, we wont have any issues.
string Foo (const myType & input); //this is fine
string Foo (myType input); //so is this
but we are not guaranteed this! the function signature could very well be
string Foo (myType & input); //asking for trouble!!
but it is awfully inconvenient to check the signature of every function we would want to pass obj to, so how can we specify that we want to only pass our object to functions that promise not to modify it?
of course one approach is to declare obj as const, but the problem with this approach is that we lose flexibility. what if we want to modify obj in Bar() after calling Foo(obj)?
void Bar ()
{
const myType obj; //default constructor
string valuableInfo = Foo(obj); //compiler will complain if Foo's signature doesnt match
//we can't modify obj here :(
//...
} //end of Bar()
The obvious but bad solution is to do this:
void Bar ()
{
myType obj; //default constructor
const myType immutableObj {obj}; //copy ctr call
//this is expensive and not recommended if obj is big! want to avoid
string valuableInfo = Foo(immutableObj); //will get the valuable Info risk free
// compiler will complain if Foo has inappropriate signature
//do other stuff
//...
} //end of Bar()
so What is the best solution here? is there a way to statically assert that Foo is non invasive to the object we pass in? can we temporarily make obj const (without having to create a new const object) or something to that effect?
Upvotes: 26
Views: 1450
Reputation: 137315
foo(std::as_const(obj));
Before C++17, if you find yourself needing to do this often, writing a helper yourself is trivial:
template<class T>
constexpr typename std::add_const<T>::type& as_const(T& t) noexcept { return t; }
// prevent accidentally creating an lvalue out of a const rvalue
template<class T> void as_const(const T&&) = delete;
Of course, nothing you do can protect against someone deliberately casting away constness. Murphy, Machiavelli, etc.
Upvotes: 23
Reputation:
I'm going to kind of suggest a roundabout solution.
of course if Foo takes obj as value or const reference, we wont have any issues.
string Foo (const myType & input); //this is fine
string Foo (myType input); // so is this
but we are not guaranteed this! the function signature could very well be
string Foo (myType & input); //asking for trouble!
I think there's something more troublesome here. What we're not seeing is the documentation of this Foo
function: its interface comments, a meaningful name, etc.
The first thing to understand about this Foo
function before we even use it are the side effects it has. If we don't know what it's going to do with the arguments we pass in without a constness guarantee (which is only a weak guarantee as pointed out and becomes weaker the more const_casts
you introduce), then I would suggest that this might point to a breakdown in the way Foo
is documented, overloaded, or the way it is being used.
Whatever Foo
is actually called, whether it's rotate
, display
, clamp
, lerp
, paint
, flip
, info
, etc., it should be clear about its side effects, and they should not vary at a logical level between overloads. Interfaces should carry even firmer guarantees with respect to invariants than a named constant about what they will and will not do.
For example, if you have an interface design like this:
/// @return A flipped 's' (no side effects).
Something flip(Something s);
/// Flips 's' (one side effect).
void flip(Something& s);
... this is an extremely problem-inducing design: a tripwire for all developers who use it, a bug nest/hive, as the overloads vary disparately in terms of their side effects. A much less confusing design would be like this:
/// @return A flipped 's' (no side effects).
Something flipped(Something s);
/// Flips 's' (one side effect).
void flip(Something& s);
... one that doesn't overload flip
based on logical side effects.
If you ever encounter a design like this and it's outside of your control, I would suggest wrapping it to something more sane like introducing that flipped
function:
/// @return A flipped 's' (no side effects).
Something flip(Something s);
/// Flips 's' (one side effect).
void flip(Something& s);
/// @return A flipped 's' (no side effects).
Something flipped(Something s)
{
flip(s);
return s;
}
... and using that flipped
function instead where you clearly understand its side effects and what it's supposed to actually do and will continue doing independent of the mutability of the arguments you pass in. While this is more roundabout than introducing a const_cast
to invoke the right immutable overload of the function, it's plugging the source of confusion at the root rather than working around a very trippy design by forcing things to be passed with constness
.
constness
is best used as a defensive mechanism for potential changes that could occur in the future, not to kind of discover/enforce the proper behavior in the present. Of course you could approach it with the rationale of guaranteeing that Foo(obj)
won't trigger side effects in obj
in the future (presuming it doesn't in the present), but at an interface level, there shouldn't be instability with respect to side effects of this sort. If Foo(obj)
doesn't modify obj
today, then it definitely shouldn't tomorrow. At the very least, an interface should be stable in that regard.
Imagine a codebase where calling abs(x)
didn't leave you feeling 100% sure whether x
would be modified or not, or at least not in the future. That's not the time to reach for constness to solve this problem: the problem here would be totally at the interface/design level with respect to abs
. There shouldn't be mutable parameter overloads of abs
that produce side effects. There shouldn't ever be anything of this sort even 10 years down the line, and that should be a firm guarantee you can depend upon without forcing your arguments to abs
to be const
. You should be able to have a similar degree of confidence for any function you use provided it's even remotely stable.
So while there may be exceptions to the rule, I would suggest to check your interfaces, make sure they document things properly, aren't overloaded in a way that produces disparate logical side effects based on which overload you use, and are stable with respect to what they're documented to do.
Upvotes: 6
Reputation: 63124
You can cast it on the spot as Brian suggested, but you can also simply use a const
reference :
myType obj;
myType const &cObj = obj;
string valuableInfo = Foo(cObj);
mutate(obj);
Upvotes: 10
Reputation: 62583
So may words... Simple explanation, there is no guarantee. I have seen tons of code which takes a value by const reference and than doing const cast on it. It is mostly prolifirate in functions which take a functor with state. Since the functor is with state, it is taken by reference, but since before rvalue references one could not pass a temporary to a function accepting a non-const reference, the signature is const reference. Than the object is const cast and the state is altered.
Upvotes: 1
Reputation: 69
You can't guarantee anything with const, nor guarantee the parameter won't be modified undesirably.
Foo()
can easily const_cast<>()
away the const
from the parameter.
Upvotes: 5
Reputation: 1723
You can use a const_cast to make it temporarily const:
Foo(const_cast<const myType>(obj));
Upvotes: 4