Diverting trains of thought, wasting precious time
When I was less experienced with C++, I would avoid creating any sort of constness in my code because it invariably led to headaches. When I'd tried, all too often I'd found myself trawling through the source code either adding or removing constness all over the place in order to make things compile. It's certainly true that constness has to be done right or not at all. Like a lot of C++, it's not resilient either to mistakes or to change.
These days I usually make a reasonable job. I've found the best way to understand constness is as follows. Every object in a C++ program exposes exactly two variants of its interface: the “full” one and the const one. The actual boundary between these, at least in the case of user-defined types, is somewhat flexible (as witnessed by mutable, and by your forgetfulness to add const to method definitions!).
Grokking the difference between a const pointer and pointer-to-const is of course a rite of passage in C and C++ programming. A more obscure subtlety about const in C++ is that const references let you pass around temporaries' lvalues, but you can't do this in a non-const fashion. It's not clear why not, except that it's something you'd rarely want to do ---unless, of course, you had omitted to add the const annotation in the signature of whatever function you wanted to pass the lvalue to. That's one reason why getting const right is something you can't really opt out of.
Today I was wrestling with an unfortunate side of const---untraceable compilation errors. I'm using the boost graph library so I can run graph algorithms over my DWARF data structure. Since all my “nodes” live in a std::map, I was specialising the graph_traits giving the map's value_type as the node type. Here's what the compiler said.
/local/scratch/srk31/opt/bin/../lib/gcc/i686-pc-linux-gnu/4.4.3/../../../../incl ude/c++/4.4.3/bits/stl_pair.h: In member function ?std::pair<const long long uns igned int, boost::shared_ptr<dwarf::encap::die> >& std::pair<const long long uns igned int, boost::shared_ptr<dwarf::encap::die> >::operator=(const std::pair<con st long long unsigned int, boost::shared_ptr<dwarf::encap::die> >&)?: /local/scratch/srk31/opt/bin/../lib/gcc/i686-pc-linux-gnu/4.4.3/../../../../incl ude/c++/4.4.3/bits/stl_pair.h:68: instantiated from ?boost::concepts::VertexLi stGraph<G>::~VertexListGraph() [with G = dwarf::encap::dieset]? /home/srk31/opt/include/boost/graph/graph_concepts.hpp:166: instantiated from ?static void boost::concept::requirement<Model>::failed() [with Model = boost::c oncepts::VertexListGraphConcept<dwarf::encap::dieset>]? /home/srk31/opt/include/boost/concept_check.hpp:43: instantiated from ?void bo ost::function_requires(Model*) [with Model = boost::concepts::VertexListGraphCon cept<dwarf::encap::dieset>]? test-6.cpp:8: instantiated from here /local/scratch/srk31/opt/bin/../lib/gcc/i686-pc-linux-gnu/4.4.3/../../../../incl ude/c++/4.4.3/bits/stl_pair.h:68: error: non-static const member ?const long lon g unsigned int std::pair<const long long unsigned int, boost::shared_ptr<dwarf:: encap::die> >::first?, can't use default assignment operator In file included from test-6.cpp:1: /home/srk31/opt/include/boost/graph/graph_concepts.hpp: In destructor ?boost::co ncepts::VertexListGraph<G>::~VertexListGraph() [with G = dwarf::encap::dieset]?: /home/srk31/opt/include/boost/graph/graph_concepts.hpp:166: instantiated from ?static void boost::concept::requirement<Model>::failed() [with Model = boost::c oncepts::VertexListGraphConcept<dwarf::encap::dieset>]? /home/srk31/opt/include/boost/concept_check.hpp:43: instantiated from ?void bo ost::function_requires(Model*) [with Model = boost::concepts::VertexListGraphCon cept<dwarf::encap::dieset>]? test-6.cpp:8: instantiated from here /home/srk31/opt/include/boost/graph/graph_concepts.hpp:188: note: synthesized me thod ?std::pair<const long long unsigned int, boost::shared_ptr<dwarf::encap::di e> >& std::pair<const long long unsigned int, boost::shared_ptr<dwarf::encap::di e> >::operator=(const std::pair<const long long unsigned int, boost::shared_ptr< dwarf::encap::die> >&)? first required here make: *** [test-6.o] Error 1
The bottom error refers to line 188 of graph_concepts.hpp, which is doing an innocuous assignment from a vertex iterator v = *p.first;. The variable v is an instance of my map entry type. Somehow, the compiler can't synthesise a copy constructor for the pointed-to vertex. This was incredibly difficult to track down because it wasn't due to any use of const in my code directly. I scoured my code for suspicious const, but to no avail. What had escaped me is that the value_type in a std::map is as follows (from Stroustrup C++PL section 17.4.1.1).
typedef pair<const Key, T> value_type;
In other words, maps are designed so that you can't update the key of an entry once they're created. This is very sane, because to do so might violate some invariant of the containing structure (imagining the popular red-black tree implementation). I hadn't noticed this limitation before because although it's common to initialise new pair objects, it's rare to update an existing one. Even though I had purposely avoided making the whole pair const in my code, there was already a const lurking in the header.
That the compiler couldn't give me a better error message (i.e. one actually pointing to the code at fault) is certainly disappointing, and will be fixed in a better compiler. Whether all this fiddling constness is a weakness with the C++ language I'm not sure. It's certainly self-consistent and well-motivated. Other languages might not have me worrying about const, but they also might not catch certain errors. Is C++'s trade-off good value? It's also not clear whether a better definition of map might not have lifted the const into individual references to the value_type... or not. I'd like to be able to rail and propose some better language design, but part of me suspects that sometimes in programming, the devil really is inescapably in the details.
[/devel] permanent link contact