-1

(UPDATE: This question stems from an implementation of a wrapper class passed by value for an object that has different meanings for const Foo and Foo, a move based entirely on strong opinions from people here. Prior, I'd been passing around const Foo* and Foo* and when the wrapper came along I swapped that for Wrapper<Foo> and const Wrapper<Foo>. It is now clear that mechanical substitution doesn't make sense, and I'm going to need something more complex, such as Wrapper<Foo> and Wrapper<const Foo>...though I don't know how to write that properly just yet. Apologies for the misunderstanding, but I'll keep this here as I actually think it's more revealing than many questions.)


In looking into this question, it seemed to boil down to being parallel to the idea that you can't do this:

const Foo defaultFoo (6502);
const Foo theFoo (getConstFoo()); // returns const Foo for privilege reasons
if (theFoo.getBar() < 2012) {
    theFoo = defaultFoo; // Error.
}
// ...you want to do const-safe methods with theFoo...

Much like references, a const value can't be retargeted. Doing the following would compile, but not be what I (in this kind of scenario) would be intending:

Foo defaultFoo (6502);
Foo& theFooRef (getFooRef());
if (theFooRef.getBar() < 2000) {
    theFooRef = defaultFoo; // Not an error, but not a retarget.
}
// ...you want to do stuff with theFooRef...

It seems (from my understanding) that reference_wrapper can work around this in the reference case, with something like:

Foo defaultFoo (6502);
std::reference_wrapper<Foo> theFooRef (getFooRef());
if (theFooRef.get().getBar() < 2000) {
    theFooRef = std::ref(defaultFoo);
}
// ...do stuff with theFooRef.get() or employ implicit cast...

I'm wondering if there's a "value_wrapper" out there which does something similar. It seems reasonable to me to want a variable which holds an item by value that is const for reasons of const-correctness...not because you aren't going to change it. (such as keeping track of the current node in a pre-order treewalk, despite only having const access to the nodes in that tree, where passing in the previous node to a function is how you get the new node)

If you wanted to be clunky, you could use std::pair<const Foo, bool> and just ignore the bool:

const Foo defaultFoo (6502);
std::pair<const Foo, bool> theFooBool (getConstFoo(), false);
if (theFooBool.first.getBar() < 2012) {
    theFooBool = std::pair<const Foo, bool> (defaultFoo, false);
}
// ...do const-safe methods with theFooBool.first...

But is there a better way of addressing this, besides implementing my own version of "value_wrapper"?

Community
  • 1
  • 1
  • 5
    I do not understand. By assigning `theFoo = defaultFoo`, you _change the value of `theFoo`_. Therefore, declaring it `const` is wrong. If you need to be able to change the value of something, do not declare it `const`. If you really want to make `theFoo` const-qualified and need to be able to select one `Foo` or another when you initialize `theFoo`, use a function or lambda expression to encapsulate the selection logic. – James McNellis Jul 12 '12 at 23:48
  • 5
    I think you're fundamentally misunderstanding what const-correctness _is_... Also, the fact that `getConstFoo` returns a `Foo const` doesn't mean that `theFoo` must itself be const; you're performing a logical copy, so whether the source of the copy is const or not is irrelevant. – ildjarn Jul 12 '12 at 23:49
  • To @JamesMcNellis and ildjarn: Pardon, but your responses strike me as parallel to someone saying "If it's a reference, you never reassign it, that's not how references work! Read a book!" But people come along and have reference_wrappers, because they find motivating scenarios for such inventions. So please, work with me on the scenario instead of assuming I *"don't understand const correctness"* (!) :-/ I did manage to use lambdas to get past some conditional initialization logic...but think about the pre-order traversal case using a const value (itself a wrapper). – HostileFork says dont trust SE Jul 12 '12 at 23:55
  • Incidentally, I'm being forced down this path because people got mad when I tried to finesse *not* creating a wrapper class which passes references around by value. As a consequence, these const-or-non-const wrapper objects are a real hassle, in terms of preserving the initial const-correctness on the objects...that were managed just fine when they were aliased pointers. If I turn them into ConstWrapper and Wrapper then I'm really ditching the features of the language.. http://stackoverflow.com/questions/11219159/make-interchangeable-class-types-via-pointer-casting-only-without-having-to-all – HostileFork says dont trust SE Jul 12 '12 at 23:58
  • @ildjarn Argh. Well maybe you're right then, and I'm not getting how these by-value wrapper objects are going to preserve const correctness. Can you read between the lines and tell me what I *do* want, by chance? :-/ The main goal is I want to have two different access levels, and it used to work but I guess the by-value semantic switch is putting the responsibility on the person who gets the value back instead of the person returning it in the contract. – HostileFork says dont trust SE Jul 13 '12 at 00:05
  • 4
    Kind of -- it seems like you want to munge the object a bit _then_ make it const. This can either be done by binding the non-const object to a const reference then only working with that const reference afterwards, or by using a lambda/factory to generate the correct object in the first place as @James suggested. (Also, unrelated, but returning const UDTs from functions as `getConstFoo` does is a bad idea, as it inhibits move semantics.) – ildjarn Jul 13 '12 at 00:06
  • I don't want to modify the object. I *used* to use pointers that were either to const or non-const objects. If someone returned a `const Foo*` to you then you were only able to call the const methods. If someone returned a `Foo*` to you then you could call both const and non-const. You could pass a `Foo*` to a method that took a `const Foo*` but not vice-versa. Now I've got a wrapper object that copies by value and it looks like indeed that ruins the contract, this is just how I "noticed". I could use a good link to anywhere a wrapper that preserves the const contract has been done...! – HostileFork says dont trust SE Jul 13 '12 at 00:14
  • 1
    Note that that's not strictly speaking what `std::reference_wrapper` was designed for. Pointers do provide indirection and are retargetable. Nothing wrong with using `std::reference_wrapper` but you make it sound like doing a pointer's job is something exotic. – Luc Danton Jul 13 '12 at 01:01
  • I am sorry if you took my comment to be flippant; that was not at all my intent. I intended only to seek clarification. I think the question makes a bit more sense in the context of the other question you linked. – James McNellis Jul 13 '12 at 06:22
  • @JamesMcNellis Not a problem, it's just hard to write questions on here and *feel* like people are a bit too quick on the "you're an idiot! downvote, slam!" trigger button (whether that's actually the case or not). :-/ I think the issue is as simple as a realization that things like optional are values which (for copyable objects) do *not* provide the "indirection step" which one must have in order to use constness as a contract. If an object is copyable, that object cannot bear the const attribute itself and expect any kind of leverage. Hence the "mystery" response of "why retarget a const?" – HostileFork says dont trust SE Jul 13 '12 at 12:40
  • @LucDanton I gather the main goal of reference_wrapper is to make it copyable so you can use them in containers/etc. It seems the ability for retargetability of a value comes from having one's type not be a raw reference and not being const...although objects may choose to disable it (such as [boost::optional](http://stackoverflow.com/questions/11459270/boostoptional-not-letting-me-reassign-const-value-types)). Dunno about "exotic" but C++ is full of a million things that depending on your experience can seem trivial or complex, it's definitely a blind man and the elephant sort of situation. – HostileFork says dont trust SE Jul 13 '12 at 12:54

2 Answers2

2

My apologies if I've overlooked something here. But since your question doesn't mention it I'm wondering if you are aware of, and have considered:

Foo defaultFoo (6502);
std::reference_wrapper<const Foo> theFooRef (getFooRef());
if (theFooRef.get().getBar() < 2000) {
    theFooRef = std::cref(defaultFoo);
}
// ...do stuff with theFooRef.get() or employ implicit cast...
Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • Thank you; the reference_wrapper is new to me, I'm meeting lots of new ideas as I [test out the suggestions of using a pass-by-value wrapper for a const-sensitive object](http://stackoverflow.com/questions/11219159/make-interchangeable-class-types-via-pointer-casting-only-without-having-to-all). It's not easy, as with many design patterns. I've mentioned what I think is the root cause of the confusion--I don't want `const Wrapper` vs. `Wrapper`, I want `Wrapper` and `Wrapper`, which is harder to figure out how to write. :-/ Any tips appreciated! – HostileFork says dont trust SE Jul 13 '12 at 01:04
  • @HostileFork : Write a `template struct Wrapper;` and a partial specialization `template struct Wrapper;`? – ildjarn Jul 13 '12 at 01:31
  • @ildjarn Thanks for that, I hadn't thought about partial template specialization. Using that approach I managed to get something that works in a way roughly like what I had before, but hopefully this approach avoids the potential violations of strict aliasing/etc. that were there before: https://gist.github.com/3106817 – HostileFork says dont trust SE Jul 13 '12 at 19:28
  • @HostileFork : Strictly speaking, `const_cast(&foo)` (line 100) _can_ cause UB (although it doesn't in your particular example), and `template friend struct std::default_delete;` (line 23) is not portable, as a standard library implementation is free to add additional template arguments as long as they're defaulted. – ildjarn Jul 13 '12 at 19:58
  • @ildjarn Thanks for the notes. The `std::default delete` issue will be resolved once I get "Foo" pointers completely out of the published interface, and always go through wrappers, so I can axe that. I'm not really sure how to get around a `const_cast` without having two separate pointer members. But perhaps the tweak I made will reduce the risk by forcing the const Wrapper to use a getAccessor() method that always returns a const Accessor, even though the member variable is non-const...? https://gist.github.com/3106817 – HostileFork says dont trust SE Jul 13 '12 at 20:36
  • @ildjarn Actually, it gets even cleaner (and more sensible) if you *derive from* the partial template specialization. I'm surprised the compiler (G++ 4.7) makes sense of it, but it seems to. https://gist.github.com/3106817 – HostileFork says dont trust SE Jul 13 '12 at 22:07
1

If you wanted to be clunky, you could use std::pair and just ignore the bool:

This clearly explains why what you want cannot be done, because this code doesn't work. I used const int rather than const Foo, but it's the same idea. This line is where it breaks:

theFooBool = std::pair<const Foo, bool> (defaultFoo, false);

The copy assignment operator is not declared const, because by definition, copy-assignment is changing the object. const is what you use when you want the object to not be changeable.

Of course, you could just use a std::reference_wrapper<const T>, which will give you const access, but allow for re-binding. Granted, it doesn't offer value semantics, but that's as close as you're going to get. In general, most people don't need or even want this, so it just hasn't come up yet.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Thanks for the nice answer, and for breaking out the compiler...I got something very close to compile, but am a bit strung out and just sort of pseudocoding to try and express the bigger question of *"hey, what's going on here"*. The real answer turns out to be that I have to figure out how to put the "const" *inside* of my passed-by-value wrapper object somewhere, instead of on the value of the wrapper itself. Any semblance that it was working was only an illusion due to the client code already matching up and self-enforcing the constness, no actual protection. :-/ – HostileFork says dont trust SE Jul 13 '12 at 00:59
  • I think what I'm asking about is technically possible--a reassignable wrapper only allowing const method calls on the contained const objects. But I'm perhaps grasping the rationale for why people don't consider this to be worth implementing in [my answer to why boost::optional won't retarget when contained type is const](http://stackoverflow.com/a/11479925/211160) *(feedback welcome)*. Any implementation won't be more efficient than a copy construction, which would have to be defined for the wrapper to work. So most people would just inject a new non-const variable and move on with life. (?) – HostileFork says dont trust SE Jul 18 '12 at 16:01