Hacker News new | past | comments | ask | show | jobs | submit login

Can you please provide some examples where this really becomes a problem, so I can better understand what you mean?

Everybody who has tried to use const in even medium-sized C++ codebases is familiar with the behavior discussed here. It's not about specific examples, it is about what happens when you have a lot of code.

Hmm, I'm not sure if codebase with millions of lines is 'medium' or 'large' but from my experience const is great even there. Of course, modifying existing code to become const correct in interfaces where previously it was not can cascade to a point where a majority of the code needs a rewrite, so I can see situations where adding const to an old interface can cause lots of grief.

But from my experience const really helps in enforcing directed graph constraints on logic and data flow which is great - and forces code to be easier to reason about.

I'm someone with a codebase where we practice const correctness and we haven't had a problem with it. We were being const correct from the start so we don't have the problems associated with trying to change that midstream.

Assuming you do it from the start you shouldn't be running in to issues.

Exactly right. But the example was of combining two code bases, one of which tried to maintain const correctness and one which had utter disregard for it. If you don't believe in const correctness in the first place, the work of making your code compatible can seem intractable.

Can you please provide some examples, so I can better understand what you mean?

I'm no C nor Ada expert, but I think he means in C `const` is a data type modifier, whereas in Ada it's a function modifier, making the concerns orthogonal. I'm never happy when I see warning about `const` being lifted by some function in C. I think that's what parent is referring to.

If you make a function that accepts an unsigned int, and then you try to pass in a const unsigned int, you will need to either typecast it, make a new function, or change the function to only accept const unsigned ints.

Although it's been a long time since I coded in C/C++, correct me if I'm wrong.

You're incorrect, for C89 through C++17. I'd recommend not making statements about languages you've forgotten.

In the C++ Working Paper, the Standardese is N4527 5.2.2 [expr.call]/4 "When a function is called, each parameter (8.3.5) shall be initialized (8.5, 12.8, 12.1) with its corresponding argument." followed by 8.5 [dcl.init] where /17.8 has a nice example:

"Note: An expression of type "cv1 T" can initialize an object of type "cv2 T" independently of the cv-qualifiers cv1 and cv2. int a; const int b = a; int c = b;"

(A "cv-qualifier" is const or volatile; they behave similarly in many contexts, hence the collective terminology for convenience.)

I stand corrected, thank you.

That sounds completely wrong, unless the function takes a reference to the unsigned int.

Const, in theory, is a fine idea. As implemented in C++ though, it's unwieldy. There's at least three (if memory serves) places you can put it in a member function declaration (not including params).

What's more, it can "infect" any other code that touches it, in turn requiring casting or weird const additions. For a simple rant, check the first few paragraphs here:


There's also two places you can put "int" in a function declaration, but nobody ever seems to complain about that ;)

I'm not even sure I understand the rant, though I've been a fan of const for years so it's probably automatic by now. So, say you have a class with a const member, and you have to pass it in to a function? Well, that's a bit unusual - const members are pretty rare in my experience. But the underlying problem sounds like: you have a const object, and you want to pass it into a function. There are three possibilities, I suppose:

1. The function takes const T & or const T * , because it doesn't change that argument. So no problem.

2. The function takes T, because it doesn't change that argument (and the object is small and/or the programmer doesn't care about the copying - irrelevant issues here). So no problem.

3. The function takes T & or T * , because it changes that argument. Compilation fails! But - no problem! Because this is what you want. You passed what's supposedly a const object into a function that expected to be able to change it. Obviously compilation has to fail.

So maybe the problem is people declaring parameters as T & or T * , when they meant const T & or const T * ?

I bet if C++ had const by default, this problem would never arise. We'd be striding, right now, arm in arm, towards our glorious const-filled destiny, singing a song about it as we went.

As I recall isn't there also "const T const &"?

No, but you can have that with pointers, since the constness of the pointer itself (i.e., whether you can change the address) is separate from the object it points to (i.e., whether it might point into ROM or not). So you can have T * (variable RAM address), T * const (fixed RAM address), const T * (variable ROM/RAM address), and const T * const (fixed ROM/RAM address).

(References, by contrast, can never be reseated. So const T &const doesn't make sense. But... I didn't try this right now to double check, so if it works - though I think it doesn't - don't blame me. Or, if you do, at least allow me to plead extenuating circumstances, in the form of C++.)

That seems right, I must've swapped that behavior with that of pointers in my head.

What the author there is calling "infecting" and "mangling" seems to be const doing exactly what it's intended to do.

You can't just declare something a constant then cast it out of being constant later? Of course not, that's the entire point!

No, see, that's the thing. It's annoying when you want to--and I apologize because it's been several years since I had to deal with this nonsense--copy elements between logically compatible containers but ones whose types are differing in layers of constness. Or if you to, say, move a reference all the way down into a pointer (or vice versa) to interop with C or other legacy code.

Or any of several other things, including some truly odd possibilities using operator and function overloading.

It's just really gross as implemented.

copy elements between logically compatible containers but ones whose types are differing in layers of constness. Or if you to, say, move a reference all the way down into a pointer (or vice versa) to interop with C or other legacy code. Or any of several other things, including some truly odd possibilities using operator and function overloading.

And this is 'const''s fault? Others have asked - can you provide an example? Because what you're describing sounds like it's going to be a hot mess, const or no const.

I don't really think that const can make bad data representation any worse than it already is.

It sounds like you wanted to do something like this:

    std::vector<char*> vec1(...);
    std::vector<char const*> vec2(...);
    std::copy(vec1.begin(), vec1.end(), vec2.begin()); // works (1)
    std::copy(vec2.begin(), vec2.end(), vec1.begin()); // fails (2)
If this compiled, the second example would invoke undefined behavior and crash if you attempted to write to an element that pointed to read-only memory (e.g., a string literal).

Container element types are almost never declared const unless their values are truly immutable (in this case the pointer is mutable, but the underlying memory is not, which is kind of strange).

I think he just meant that: void foo(std::vector<char const* > &vec2) { ... }

    std::vector<char*> vec1(...);
    foo(vec1); // fails
The idea is that you should be allowed to pass non-const vector to const function who accepts same vector (const or not const) with const variables. Because the function foo is like saying "give me vector, I will not modify it's elements", and with current C++ standard this is not allowed.

In that case, the question should be, why am I attempting to convert one concrete type to another concrete type?

If you're writing a program, you should use the same type, std::vector<char* >, everywhere.

If you're writing a library, the API should avoid including container details in function parameters, and const and non-const T should be allowed.

    template<typename Container>
    void foo1(Container const& c);

    template<typename Iterator>
    void foo2(Iterator first);

    template<typename Iterator>
    void foo3(Iterator first, Iterator last);

    template<typename Iterator>
    void foo4(Iterator first, std::size_t num);

    template<typename T>
    void foo5(std::vector<T> const& vec);

    void foo6(char const* const* first, std::size_t num);
foo6 is a pretty good alternative here, imo:

    std::vector<char*> vec(...);
    foo6(&vec.front(), vec.size());
It would be cool if the compiler could figure templated type conversion out, but the crux of the issue is, as I understand it, that T<A> and T<A const> are not necessarily implemented the same way.

Also note that std::vector<std::string const> won't compile -- it wouldn't be copy-assignable -- so these issues mostly seem to arise specifically when dealing with C strings and/or C compatibility. And reinterpret_cast is amazingly helpful for porting from C.

Even if you ignore the way that the types of template instantiations are (for good or for ill) unrelated, these two types are not compatible. Say this particular conversion were permitted. You have your function. (String literals are const char * .)

    void blah(vector<const char * > &xs) {
And you have the code calling it:

    vector<char * > xs;
And that would not be valid. The rationale for the conversion rules for pointers to pointers is based on a similar sort of problem. (In fact I think it's exactly the same problem, at any level, but that only just occurred to me, so perhaps not.)

(This isn't to say there aren't other similar sorts of conversion that would be safe, and that the language perhaps should support, just that this isn't one of the valid ones.)

But that's exactly what is const is created for — preventing you from doing that. Why would you want to break the safeguards somebody has deliberately put in place?

You are arguing with the Java People(tm), you cannot win this.

Also: http://yosefk.com/c++fqa/const.html

Looks like someone who does not even understand C++ in the first place...

If you're complaining about const functions, as in "int foo(void) const;," it helps to think of it as declaring that the implicit "this" pointer being passed into the function as its first argument is being cast to type "FooType const * " rather than "FooType * ." The reason it has to go at the end of the function declaration is that member functions always have an implicit first argument unless they are static. This is also why you can't declare free functions as const functions. They don't have a "this" pointer with associated fields to manipulate.

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact