26

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?

ForeverStudent
  • 2,487
  • 1
  • 14
  • 33

7 Answers7

23

In C++17, thanks to P0007R1:

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.

T.C.
  • 133,968
  • 17
  • 288
  • 421
21
Foo(static_cast<const myType&>(obj));
Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • 8
    Why do you prefer `static_cast` to `const_cast` in this case? – wchargin Dec 05 '15 at 00:42
  • 2
    @WChargin `static_cast` can add constness, but not remove it, which makes it much safer than `const_cast` – milleniumbug Dec 05 '15 at 10:32
  • 10
    @milleniumbug OTOH, `static_cast` can change the underlying type. `const_cast` can't. – T.C. Dec 05 '15 at 11:55
  • 1
    IMO `const_cast` is much more clear. The fact you're adding `const` via `const_cast` is obvious, and accidentally `static_casting` to a different underlying type is a much bigger potential accident. – fluffy Dec 05 '15 at 21:48
  • 1
    `const_cast` could be used to accidentally remove `volatile` – user253751 Dec 05 '15 at 22:05
  • I usually use `static_cast` for implicit conversions. It's just a habit. I don't really have a strong argument for using `static_cast` rather than `const_cast`. – Brian Bi Dec 07 '15 at 22:23
10

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);
Community
  • 1
  • 1
Quentin
  • 62,093
  • 7
  • 131
  • 191
  • Doesn't this invoke UB if you mutate `obj` between the creation of `cObj` and some use of it? – user2357112 Dec 05 '15 at 00:29
  • @user2357112 not at all, why do you think it would ? – Quentin Dec 05 '15 at 00:31
  • 1
    `const` doesn't guarantee that the backing data for an object won't be modified, it only guarantees that *you* won't modify it. (And thanks to `const_cast` it's not even a very good guarantee.) – fluffy Dec 05 '15 at 00:34
  • 2
    @fluffy If someone tampers with your object via `const_cast` in a way you didn't account for, they're sent straight to UB land. That's a pretty good guarantee, formally speaking. – Quentin Dec 05 '15 at 01:00
  • @Quentin Sure, but `const` is still a pretty week guarantee - you can send a `const` ref to thing A (which retains it) and a non-`const` ref to thing B (which mutates it). You only know that thing A (hopefully) won't mutate it; thing A can't guarantee that it won't be mutated on it. – fluffy Dec 05 '15 at 21:29
6

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.

5

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.

  • that is a scary thought...can you elaborate? that is the most devious thing I ever heard. to declare a function that takes in an object with const modifier, then strip the modifier away and mess around with it? – ForeverStudent Dec 04 '15 at 21:23
  • 5
    Indeed. While it is not invalid per se to write such a function. Now you're *really* asking for trouble as the first time that function is called with an object that actually *is* `const`, you get undefined behavior. In C++, you can always invoke undefined behavior by writing sufficiently bad code. I think the point is to prevent *accidential* bugs, not to guard against any potential abuse. The latter is not possible in C++. – 5gon12eder Dec 04 '15 at 21:28
  • 1
    How nihilistic of you ;) – Jordan Melo Dec 09 '15 at 20:06
4

You can use a const_cast to make it temporarily const:

Foo(const_cast<const myType>(obj));
tahsmith
  • 1,643
  • 1
  • 17
  • 23
  • This is a good answer. The price to pay is an additional copy (due to value casting and not reference casting), but it saves you other headaches. – vsoftco Dec 04 '15 at 21:32
  • Of course, once you're making a copy anyway, you wouldn't be hurt by calling a modifying operation so the `const` isn't really needed any more. But depending on how expensive a copy is to make, I'd be hesitant to use it just because I'm afraid I could forget that a function modifies its argument. – 5gon12eder Dec 04 '15 at 21:40
1

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.

SergeyA
  • 61,605
  • 5
  • 78
  • 137
  • I think writing such function is a really bad idea. If you really want to have those semantics, the to-be-modified members of the functor should be declared `mutable`. This way, you won't get undefined behavior if the function is called with a `const` object. – 5gon12eder Dec 04 '15 at 21:31
  • I am not arguing it is a good idea, I am saying such code is in abudance. – SergeyA Dec 04 '15 at 21:35