4

I was trying to write down some implementations for a couple of data structures that I'm interested in for a multithreaded / concurrent scenario.

A lot of functional languages, pretty much all that I know of, design their own data structures in such a way that they are immutable, so this means that if you are going to add value to an instance t1 of T, you really get a new instance of T that packs t1 + value.

 container t;
 container s = t; //t and s refer to the same container.
 t.add(value); //this makes a copy of t, and t is the copy

I can't find the appropriate keywords to do this in C++11; there are keywords, semantics and functions from the standard library that are clearly oriented to the functional approach, in particular I found that:

  • mutable it's not for runtime, it's more likely to be an hint for the compiler, but this keyword doesn't really help you in designing a new data structure or use a data structure in an immutable way
  • swap doesn't works on temporaries, and this is a big downside in my case

I also don't know how much the other keywords / functions can help with such design, swap was one of them really close to something good, so I could at least start to write something, but apparently it's limited to lvalues .

So I'm asking: it's possible to design immutable data structure in C++11 with a functional approach ?

Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
user2485710
  • 9,451
  • 13
  • 58
  • 102
  • You should read more about c++ - your question is plain confusion. –  Mar 03 '14 at 23:29
  • @DieterLücking where the confusion arise ? – user2485710 Mar 03 '14 at 23:29
  • 2
    **Of course** it's possible. What makes you think it isn't, more specifically, what about it seems hard/impossible to you? –  Mar 03 '14 at 23:30
  • @delnan well, can you show something that the language is able to build and make it behave like it's immutable ? – user2485710 Mar 03 '14 at 23:35
  • 2
    The best you can do is to declare *instances* of your data structure always with the `const` qualifier. The thing is that you wouldn't gain as much as when compared to a true functional language like for example Haskel. Such languages *highly make use* of the fact that all values are constants, and each expression only depends on itself (and no other state). This is simply not the case with C++, regardless of the design of your data structure. What you probably want is to tell the compiler that only const instances of your class are allowed, which is not possible. – leemes Mar 03 '14 at 23:35
  • 1
    It's easy to design an immutable data structure... just create a container with a data member such as one of the existing Standard library data structures, then make the mutating functions do a deep copy, mutation of the copy, then return it by value. You could use such objects from any type of code. Not particularly scalable, but all a matter of picking your poison. – Tony Delroy Mar 03 '14 at 23:35
  • 1
    @user2485710: `const std::vector` is our standard immutable data structure. – Mooing Duck Mar 03 '14 at 23:35
  • @MooingDuck where is the part of the standard that grants that such thing is even true ? – user2485710 Mar 03 '14 at 23:36
  • @TonyD exactly, there are ways to mimic something functional, but nothing really immutable, so nothing really functional. – user2485710 Mar 03 '14 at 23:37
  • @user2485710: § 23.3.6 in the January 2012 C++11 draft. const objects are immutable (except for extremely rare situations that don't occur in data structures). Why do you claim these are not immutable? – Mooing Duck Mar 03 '14 at 23:38
  • 1
    @user2485710: the Standard makes guarantees that you can have multiple readers accessing the Standard containers concurrently as long as no non-`const` functions are also being called... that effectively means the containers must be thread-safe anywhere they do mutate, but in practice they have no use for `mutable` data fields that would need explicit thread safety. – Tony Delroy Mar 03 '14 at 23:40
  • 1
    @user2485710: I don't see a difference... if the type doesn't provide any public functions to mutate it (after construction), how is it "not really immutable" in a way that frustrates functional programming? – Tony Delroy Mar 03 '14 at 23:42
  • @MooingDuck quick question, according to what you just posted, what can be identified as `object` in your example ? The vector class ? the elements, ask yourself this kind of questions. `const` it's not going to solve my problem. – user2485710 Mar 03 '14 at 23:42
  • @TonyD according to what you just said a plain simple `std::vector` is equivalent, a standard vector is already thread-safe when I'm just reading from it ! I need to do something with the design of the data structure, I'm not trying to qualify an instance of my data structure. – user2485710 Mar 03 '14 at 23:44
  • @user2485710: Both the vector _and_ the elements. The question is irrelevant to my claims. According to the question as written, `const std::vector` appears to solve the problem. If it does _not_ solve your problem, can you try to clearly explain why it does not? Apparently we're all misunderstanding what you're trying to do. – Mooing Duck Mar 03 '14 at 23:45
  • @MooingDuck you have an instance of `T` labeled `t`, you add a `value`, your functional language changes the real object/instance pointed by `t` without you noticing that, because `T` is designed to be immutable, not because you qualified `t` to behave like that; it's by design. – user2485710 Mar 03 '14 at 23:48
  • 1
    @user2485710: a `std::vector` has public mutating functions, like `push_back` and `erase`. What I've said is that you can write a `class immutable_vector` that uses Composition - the `vector` data member's mutator functions can be removed from the public API, or replaced with versions that return new `immutable_vector`s by value. – Tony Delroy Mar 03 '14 at 23:48
  • @user2485710: Ah, that would require _very_ crafty C++ designed to work like C# or Java under the covers. Basically it would revolve around `std::shared_ptr>`, plus a wrapper like TonyD has been suggesting. – Mooing Duck Mar 03 '14 at 23:49
  • 1
    @user2485710: your description to MooingDuck above makes it sound like you want a reference counted object with copy-on-write. That's fine as long as your API's restrictive enough to make it work. The Standard container API's aren't - they have operations like `[n]` that can return references that may be read or written, and doing a pessimistic copy each time is too slow. Iterators taken before the mutating action are another issue. But, if you want to design a more restrictive container API it's easy to do. – Tony Delroy Mar 03 '14 at 23:53
  • @TonyD like rewriting the `push_back` member function to do the work under the hood ? You are suggesting this kind of approach ? – user2485710 Mar 03 '14 at 23:54
  • Yes... `immutable_vector::push_back` would need to make a copy before doing the `push_back`... if you want to have the client API "feel" like the operation is mutating a particular object rather than returning a new one by value, then you need to wrap a shared pointer like Mooing Duck has said. – Tony Delroy Mar 03 '14 at 23:56
  • 2
    [Start reading](http://bartoszmilewski.com/2013/11/13/functional-data-structures-in-c-lists/). – Casey Mar 03 '14 at 23:59
  • As for swap, are you after something like `template std::pair reversed(const std::pair& pair) { return make_pair(pair.second, pair.first); }`...? – Tony Delroy Mar 04 '14 at 00:00
  • @TonyD nope for the `swap` thing, my real problem is that `swap` has no `T&&` signature. – user2485710 Mar 04 '14 at 00:03
  • @Casey thanks +2moretogo – user2485710 Mar 04 '14 at 00:04
  • @delnan Actually C++ cannot do many fundamental aspects of persistent data structures, which are typically the structures required/expected by a truly functional language. See here: https://github.com/BartoszMilewski/Okasaki/issues/1 – johnbakers Jan 01 '16 at 17:42
  • @johnbakers That's a single example of an implementation issue: it is fully possible to work around this problem by destroying the tail of the list in a loop (you just can't use `std::shared_ptr` for it), the author is just not willing to do so. Note that he carefully chooses the wording "not an optimal implementation language", rather than the false blanket assertion you make. Whatever property of C++ you think makes persistent data structures fundamentally impossible, I guarantee you there's a workaround. Whether the resulting code will be practical or idiomatic is another question. –  Jan 01 '16 at 18:10
  • @delnan well it would certainly be awesome if someone were to indeed provide a robust, stable implementation of these structures, because it would open up new avenues of development in this great language. – johnbakers Jan 01 '16 at 19:12

4 Answers4

6

You simply declare a class with private member variables and you don't provide any methods to change the value of these private members. That's it. You initialize the members only from the constructors of the class. Noone will be able to change the data of the class this way. The tool of C++ to create immutable objects is the private visibility of the members.

mutable: This is one of the biggest hacks in C++. I've seen at most 2 places in my whole life where its usage was reasonable and this keyword is pretty much the opposite of what you are searching for. If you would search for a keyword in C++ that helps you at compile time to mark data members then you are searching for the const keyword. If you mark a class member as const then you can initialize it only from the INITIALIZER LIST of constructors and you can no longer modify them throughout the lifetime of the instance. And this is not C++11, it is pure C++. There are no magic language features to provide immutability, you can do that only by programming smartly.

pasztorpisti
  • 3,760
  • 1
  • 16
  • 26
  • actually you can still modify an instance qualified as `const` and the meaning of `const` changed a little in C++11; I can't see any implementation in your post, nor a theoretical example of this. – user2485710 Mar 03 '14 at 23:34
  • 2
    I use `mutable` occasionally, for delay-loading a member. – Mooing Duck Mar 03 '14 at 23:34
  • 3
    mutable is nice for lazy evaluations –  Mar 03 '14 at 23:37
  • 2
    @user2485710, ask a vague question with no code, get a vague answer with no code. If you want more specific advice you'll need to be more specific in your question. – Mark Ransom Mar 03 '14 at 23:37
  • 1
    ... and hidden caching ;) – leemes Mar 03 '14 at 23:37
  • @Mooing Duck In most cases using mutable isn't really reasonable. Delay loading is a wrong example for using mutable in my opinion. If a member is delay loaded then the object isn't immutable anyway... – pasztorpisti Mar 03 '14 at 23:38
  • @MarkRansom the thing is that I have found nothing, the closest things to describe the solution of my problem are `mutable` which is the exact opposite of what I want ( and the opposite of mutable it's not `const` ) and `swap` which works fine in theory but in practice it's unusable. – user2485710 Mar 03 '14 at 23:39
  • 2
    I think you got an exact recipe to create an immutable class, you don't need magical C++11 keywords for that. @leemes Two reasonable examples for mutable I can recall: Intrusive refcounting for const/immutable objects, and maybe some debug counters/variables... In most cases the object probably isn't truly const or its mutable/non-const part shouldn't be an immutable member, rather another referenced object. – pasztorpisti Mar 03 '14 at 23:43
  • @pasztorpisti What exactly are you referencing in your comment? My comment named a third use of mutable (it was a reponse to Mooning Duck's and Dieter's use cases. – leemes Mar 03 '14 at 23:48
  • @pasztorpisti I got something even before posting this, but it's a real "immutable by design" or something even close to how data structures behaves in functional languages. – user2485710 Mar 03 '14 at 23:49
  • @leemes fair, your post was more near so I spot your name more quickly! :-) – pasztorpisti Mar 03 '14 at 23:49
  • 1
    In functional languages basically everything is immutable, when we are talking about Haskell and C/C++ we are talking about two paradigms. If you want to make a functional language from C/C++ you will be very disappointed. The toolset of C/C++ spans from very low (near-assembly) level to very high level and allow for a lot of hacks compared to the sophisticated type-safe design of Haskell. You won't find a magic keyword with what you just prefix your class declaration or something else to get a hack-free immutable class... – pasztorpisti Mar 03 '14 at 23:53
  • @pasztorpisti the thing is that the language can do very little if at runtime there are really not too many keywords that can help and most of them do work at compile-time. the type system of a functional language is always designed for the runtime phase, that's the problem. but it's probably not even the typing system the real problem, the fact is that, for example, there are really no keywords that are the opposite of `mutable`. – user2485710 Mar 03 '14 at 23:58
  • 2
    @user2485710: except `const` – Mooing Duck Mar 04 '14 at 00:43
3

In c++ "immutability" is granted by the const keyword. Sure - you still can change a const variable, but you have to do it on purpose (like here). In normal cases, the compiler won't let you do that. Since your biggest concern seems to be doing it in a functional style, and you want a structure, you can define it yourself like this:

class Immutable{
   Immutable& operator=(const Immutable& b){} // This is private, so it can't be called from outside
   const int myHiddenValue;
public:
   operator const int(){return myHiddenValue;}
   Immutable(int valueGivenUponCreation): myHiddenValue(valueGivenUponCreation){}
};

If you define a class like that, even if you try to change myHiddenValue with const_cast, it won't actually do anything, since the value will be copied during the call to operator const int.

Note: there's no real reason to do this, but hey - it's your wish.

Also note: since pointers exist in C++, you still can change the value with some kind of pointer magic (get the address of the object, calc the offset, etc), but you can't really help that. You wouldn't be able to prevent that even when using an functional language, if it had pointers.

And on a side note - why are you trying to force yourself in using C++ in a functional manner? I can understand it's simpler for you, and you're used to it, but functional programming isn't often used because of its downfalls. Note that whenever you create a new object, you have to allocate space. It's slower for the end-user.

Community
  • 1
  • 1
Paweł Stawarz
  • 3,952
  • 2
  • 17
  • 26
  • 1
    because immutable data structures really really help you when concurrency is involved, and, the big plus is that everything happens by design so you are better suited than when you just use a bunch of mutexes or locks or trying to do fancy things. – user2485710 Mar 04 '14 at 00:01
  • If you're not trying to change them in the first place, then what's the difference? The answer to your question is: simply write a function taking an argument of type X, and returning type X. – Paweł Stawarz Mar 04 '14 at 00:02
  • that really it's not what immutable data structures are for, assuming you have `t` as an instance of `T` you can change `t` as much as you want, the magic is in the design of `T`. First lines from this http://bartoszmilewski.com/2013/11/13/functional-data-structures-in-c-lists/ – user2485710 Mar 04 '14 at 00:05
  • 1
    @user2485710 You're wrong. The magic is in the functions you call on `T`, not in `T` itself. There's nothing magical in functional languages. You just need functions of type `const T f(const T t)` that take the "old" `t` and return a "new", changed one, without touching the first one. That's how I was taught when learning SML. – Paweł Stawarz Mar 04 '14 at 00:12
  • @user2485710 Maybe I just learned the wrong functional languages (predominantly scheme/clos really), but at least in every functional language I've seen you can still mutate state if you really want. Which means you can write mutable data structures in functional languages just as well if you don't take care of your operations. Anyhow immutable data structures are extremely useful in C++ too agreed - concurrency is hard enough already without mutable data structures everywhere. – Voo Mar 04 '14 at 01:06
3

Bartoz Milewski has implemented Okasaki's functional data structures in C++. He gives a very thorough treatise on why functional data structures are important for concurrency. In that treatise, he explains the need in concurrency to construct an object and then afterwards make it immutable:

Here’s what needs to happen: A thread has to somehow construct the data that it destined to be immutable. Depending on the structure of that data, this could be a very simple or a very complex process. Then the state of that data has to be frozen — no more changes are allowed.

As others have said, when you want to expose data in C++ and have it not be available for changing, you make your function signature look like this:

class MutableButExposesImmutably
{
    private:
        std::string member;
    public:
        void complicatedProcess() { member = "something else"; } // mutates
        const std::string & immutableAccessToMember() const {
            return member;
        }
};

This is an example of a data structure that is mutable, but you can't mutate it directly.

I think what you are looking for is something like java's final keyword: This keyword allows you to construct an object, but thereafter the object remains immutable.

You can do this in C++. The following code sample compiles. Note that in the class Immutable, the object member is literally immutable, (unlike what it was in the previous example): You can construct it, but once constructed, it is immutable.

#include <iostream>
#include <string>
using namespace std;

class Immutable
{
    private:
        const std::string member;
    public:
        Immutable(std::string a) : member(a) {}
        const std::string & immutable_member_view() const { return member; }
};


int main() {
    Immutable foo("bar");
    // your code goes here
    return 0;
}
djhaskin987
  • 9,741
  • 4
  • 50
  • 86
  • 2
    Bartoz Milewski has pointed out that C++ is *not* a good language for the persistent data structure implementation that is generally considered a pre-requisite for any good functional language. In particular, Bartoz Milewski says that lack of proper tail call optimization or garbage collection makes any C++ implementation of Okasaki's ideas (which are used in Clojure and other languages) very fragile in C++. His opinion, which is shared by Clojure's creator Rich Hickey, also a C++ master, is that proper immutable functional structures are not something C++ is good at. – johnbakers Jan 01 '16 at 17:32
  • Thanks for the input @johnbakers ! I am trying to implement something that *needs* these types of structures in C++. The feedback that I'm probably doing it wrong is valuable :) – djhaskin987 Jan 01 '16 at 17:38
  • 2
    It's not just that you are doing it wrong, it's that you really cannot do this in C++ the way it is typically expected in a proper functional language. See here for more insight: https://github.com/BartoszMilewski/Okasaki/issues/1 In my opinion, one should not try to force a language to do something it is not good at. Use all the fantastic strengths of C++ in the way they were designed, and rethink your C++ code with those idioms in mind, or, use a different language. – johnbakers Jan 01 '16 at 17:41
  • 1
    Java's final doesn't make objects immutable. It makes references immutable. It is like `int * const`, not `int const *`. That is a big difference. – Tim Seguine Mar 19 '17 at 14:00
1

Re. your code example with s and t. You can do this in C++, but "immutability" has nothing to do with that question, if I understand your requirements correctly!

I have used containers in vendor libraries that do operate the way you describe; i.e. when they are copied they share their internal data, and they don't make a copy of the internal data until it's time to change one of them.

Note that in your code example, there is a requirement that if s changes then t must not change. So s has to contain some sort of flag or reference count to indicate that t is currently sharing its data, so when s has its data changed, it needs to split off a copy instead of just updating its data.

So, as a very broad outline of what your container will look like: it will consist of a handle (e.g. a pointer) to some data, plus a reference count; and your functions that update the data all need to check the refcount to decide whether to reallocate the data or not; and your copy-constructor and copy-assignment operator need to increment the refcount.

M.M
  • 138,810
  • 21
  • 208
  • 365