tags:

views:

409

answers:

11

I often find myself having to define two versions of a function in order to have one that is const and one which is non-const (often a getter, but not always). The two vary only by the fact that the input and output of one is const, while the input and output of the other is non-const. The guts of the function - the real work, is IDENTICAL.

Yet, for const-correctness, I need them both. As a simple practical example, take the following:

inline const ITEMIDLIST * GetNextItem(const ITEMIDLIST * pidl)
{
    return pidl ? reinterpret_cast<const ITEMIDLIST *>(reinterpret_cast<const BYTE *>(pidl) + pidl->mkid.cb) : NULL;
}

inline ITEMIDLIST * GetNextItem(ITEMIDLIST * pidl)
{
    return pidl ? reinterpret_cast<ITEMIDLIST *>(reinterpret_cast<BYTE *>(pidl) + pidl->mkid.cb) : NULL;
}

As you can see, they do the same thing. I can choose to define one in terms of the other using yet more casts, which is more appropriate if the guts - the actual work, is less trivial:

inline const ITEMIDLIST * GetNextItem(const ITEMIDLIST * pidl)
{
    return pidl ? reinterpret_cast<const ITEMIDLIST *>(reinterpret_cast<const BYTE *>(pidl) + pidl->mkid.cb) : NULL;
}

inline ITEMIDLIST * GetNextItem(ITEMIDLIST * pidl)
{
    return const_cast<ITEMIDLIST *>(GetNextItem(const_cast<const ITEMIDLIST *>(pidl));
}

So, I find this terribly tedious and redundant. But if I wish to write const-correct code, then I either have to supply both of the above, or I have to litter my "consumer-code" with const-casts to get around the problems of having only defined one or the other.

Is there a better pattern for this? What is the "best" approach to this issue in your opinion:

  • providing two copies of a given function - the const and non-const versions
  • or just one version, and then requiring consumers of that code to do their casts as they will?

Or is there a better approach to the issue entirely? Is there work being done on the language itself to mitigate or obviate this issue entirely?

And for bonus points:

  • do you find this to be an unfortunate by-product of the C++ const-system
  • or do you find this to be tantamount to touching the very heights of mount Olympus?

EDIT:

If I supply only the first - takes const returns const, then any consumer that needs to modify the returned item, or hand the returned item to another function that will modify it, must cast off the constness.

Similarly, if I supply only the second definition - takes non-const and returns non-const, then a consumer that has a const pidl must cast off the constness in order to use the above function, which honestly, doesn't modify the constness of the item itself.

Maybe more abstraction is desirable:

THING & Foo(THING & it);
const THING & Foo(const THING & it);

I would love to have a construct:

const_neutral THING & Foo(const_neutral THING & it);

I certainly could do something like:

THING & Foo(const THING & it);

But that's always rubbed me the wrong way. I am saying "I don't modify the contents of your THING, but I'm going to get rid of the constness that you entrusted me with silently for you in your code."

Now, a client, which has:

const THING & it = GetAConstThing();
...
ModifyAThing(Foo(it));

That's just wrong. GetAConstThing's contract with the caller is to give it a const reference. The caller is expected NOT TO MODIFY the thing - only use const-operations on it. Yes, the caller can be evil and wrong and cast away that constness of it, but that's just Evil(tm).

The crux of the matter, to me, is that Foo is const-neutral. It doesn't actually modify the thing its given, but its output needs to propagate the constness of its argument.

NOTE: edited a 2nd time for formatting.

+2  A: 

Your functions are taking a pointer to a pidl which is either const or non-const. Either your function will be modifying the parameter or it won't - choose one and be done with it. If the function also modifies your object, make the function non-const. I don't see why you should need duplicate functions in your case.

Mark Ransom
His function doesn't modify the object, but he returns a pointer to that object. His const function should return a pointer to const, and his non-const function should return a pointer to non-const. However, the logic used to obtain the pointer is otherwise exactly the same. Overall, this is a real use case, and a real problem.
Pavel Minaev
A: 

I would posit that if you need to cast off the const of a variable to use it then your "consumer" code is not const correct. Can you provide a test case or two where you are running into this issue?

joshperry
+3  A: 

The real problem here appears to be that you're providing the outside world with (relatively) direct access to the internals of your class. In a few cases (e.g., container classes) that can make sense, but in most it means you're providing low-level access to the internals as dumb data, where you should be looking at the higher-level operations that client code does with that data, and then provide those higher-level operations directly from your class.

Edit: While it's true that in this case, there's apparently no class involved, the basic idea remains the same. I don't think it's shirking the issue either -- I'm simply pointing out that while I agree that it is an issue, it's only that arises only rather infrequently.

I'm not sure low-level code justifies such things either. Most of my code is much lower level than most people ever have much reason to work with, and I still only encounter it rather infrequently.

Edit2: I should also mention that C++ 0x has a new definition of the auto keyword, along with a new keyword (decltype) that make a fair number of things like this considerably easier to handle. I haven't tried to implement this exact function with them, but this general kind of situation is the sort of thing for which they're intended (e.g., automatically figuring out a return type based on passed arguments). That said, they normally do just a bit more than you want, so they might be a bit clumsy (if useful at all) for this exact situation.

Jerry Coffin
There is no "his class" here. `ITEMIDLIST` is a struct in Win32 API, and it seems that this is just a helper function for it.
Pavel Minaev
That's just shirking the issue. Its a fine and dandy ideal to shoot for: expose no entrails. But we're in C++ land, not C# or VB land. C++ is very much commonly used as a glue layer between very low level systems, and it is not uncommon to have C PODS that must be manually manipulated for interfacing with the OS or some other legacy library.
Mordachai
Not necessarily, I (for one) use private accessors far more often than direct member access.
Justin
Mordachai
A: 

You don't need two versions in your case. A non-const thing will implicitly convert to a const thing, but not vice versa. From the name of you function, it looks like GetNextItem will have no reason to modify pidl, so you can rewrite it like this:

inline ITEMIDLIST * GetNextItem(const ITEMIDLIST * pidl);

Then clients can call it with a const or non-const ITEMIDLIST and it will just work:

ITEMIDLIST* item1;
const ITEMIDLIST* item2;

item1 = GetNextItem(item1);
item2 = GetNextItem(item2);
cwick
I extrapolate on this "non-solution" in my edit. Sorry for not doing that in the original post. But this is an evil non-const-correct approach, and one I cannot get behind.
Mordachai
C++'s const checking is a helpful service, not a chore to be disabled or subverted.
Mike Elkins
This is how the C standard library solves the problem. See the prototypes for strchr or strstr. Both take a const pointer and return a non-const pointer to the same chunk of memory.
chazomaticus
But then it seems to me, the const keyword has practically no meaning in the land of C-strings other than documentation-in-code. See how nicely it opens the back door to "modifying literals": `char* p = strchr("Hello", 'l'); *p = 'L';`
UncleBens
In fact I just noticed that in C++ strchr has two versions:`const char* strchar(const char*, char)`and`char* strchar(char*, char)`which would prevent the kind of misue UncleBens is talking about. So I guess this is kind of like putting a safety on a gun ; it's there to prevent accidental misfire, not to make it impossible for somebody to fire the gun.
cwick
UncleBens: I guess you could say that door is already open? The following C code is perfectly legal, though will cause a crash: `char*p="abcd"; *p='z';`. You don't need strchr to be able to write bad code.
chazomaticus
+3  A: 

I don't believe it's the deficiency of const-correctness per se, but rather the lack of convenient ability to generalize a method over cv-qualifiers (in the same way we can generalize over types via templates). Hypothetically, imagine if you could write something like:

template<cvqual CV>
inline CV ITEMIDLIST* GetNextItem(CV ITEMIDLIST * pidl)
{
    return pidl ? reinterpret_cast<CV ITEMIDLIST *>(reinterpret_cast<CV BYTE *>(pidl) + pidl->mkid.cb) : NULL;
}

ITEMIDLIST o;
const ITEMIDLIST co;


ITEMIDLIST* po = GetNextItem(&o); // CV is deduced to be nothing
ITEMIDLIST* pco = GetNextItem(&co); // CV is deduced to be "const"

Now you can actually do this kind of thing with template metaprogramming, but this gets messy real quick:

template<class T, class TProto>
struct make_same_cv_as {
    typedef T result;
};

template<class T, class TProto>
struct make_same_cv_as<T, const TProto> {
    typedef const T result;
};

template<class T, class TProto>
struct make_same_cv_as<T, volatile TProto> {
    typedef volatile T result;
};

template<class T, class TProto>
struct make_same_cv_as<T, const volatile TProto> {
    typedef const volatile T result;
};

template<class CV_ITEMIDLIST>
inline CV_ITEMIDLIST* GetNextItem(CV_ITEMIDLIST* pidl)
{
    return pidl ? reinterpret_cast<CV_ITEMIDLIST*>(reinterpret_cast<typename make_same_cv_as<BYTE, CV_ITEMIDLIST>::result*>(pidl) + pidl->mkid.cb) : NULL;
}

The problem with the above is the usual problem with all templates - it'll let you pass object of any random type so long as it has the members with proper names, not just ITEMIDLIST. You can use various "static assert" implementations, of course, but that's also a hack in and of itself.

Alternatively, you can use the templated version to reuse the code inside your .cpp file, and then wrap it into a const/non-const pair and expose that in the header. That way, you pretty much only duplicate function signature.

Pavel Minaev
I badly want to attend a really good intensive template type programming course. The above looks insane - especially for this particular problem. And is the source of my current reluctance to really delve deeply into template type metaprogramming. It just looks like it grows out of control quickly, and I lose the thread of the actual core of what I'm doing too quickly. Language tools are great if I can grok them deeply enough to really leverage them, and offer a lever to my consumers. But if it leaves my jaw hanging, and is opaque to my consumers... I have to question its eal utilty.
Mordachai
[Please don't misunderstand the scope of my criticism of templates: they have huge uses. I am only questioning the ultimate utility of type metaprogramming, at least for me, as I have yet to grok it deeply enough to really make it a tool and not a headache :) ]
Mordachai
TMP as applied here is really just a way of saying "if you give me type T with traits X, I give you type U with the same trait X". By itself, I believe the concept is immensely useful, as it allows you to write strongly typed code where you wouldn't be able to otherwise. But I agree that, broadly speaking, C++ approach to this is messy.
Pavel Minaev
Interesting approach. The "make_same_cv_as" templates can be useful in other parts of the code. But on the other hand, you will have this messieness only once. BTW, wouldn't it be possible to omit the last "make_same_cv_as" and use it twice in the reinterpret_cast? Even more messy ;-)
drhirsch
To each their own. I find the intent of `make_same_cv_as` much more clear than the `reinterpret_casts` of the rest of the code. It's just a bit verbose (BTW, isn't it missing a `typename` keyword?)
UncleBens
Good catch about `typename`, fixed.
Pavel Minaev
I gave this the "answer" status because it both points out a better language feature (cvqual idea), and a reasonable solution using TMP. However, there are other answers with less rigorous but possibly more functional approaches (such as is used in typical STL implementations and my initial example), and ultimately my feeling is that this is an awful lot of work just to avoid a little correct casting. Personally, I resonate most with "Its a shortcomming of C++", and unless you're writing something akin to the STL, TMP is overkill. Casting or code duplication is, sadly, the lesser evil.
Mordachai
A: 

I think it's hard to get around, if you look at something like vector in the STL, you have the same thing:

iterator begin() {
    return (iterator(_Myfirst, this));
}
const_iterator begin() const {
    return (iterator(_Myfirst, this));
}

/A.B.

Andreas Brinck
I almost mentioned the STL and just such an example. I know my example doesn't go so far, but ever since I switched back to C++ from Smalltalk I have found that const-correctness often costs me a great deal of effort to get it right. I maintain a lot of legacy C code which makes no qualms with doing all sorts of const-incorrect things, dangerous casts, etc., and the work involved in upgrading all of that code just for const-correctness is astounding.
Mordachai
+5  A: 

IMO this is an unfortunate by-product of the const system, but it doesn't come up that often: only when functions or methods give out pointers/references to something (whether or not they modify something, a function can't hand out rights that it doesn't have or const-correctness would seriously break, so these overloads are unavoidable).

Normally, if these functions are just one short line, I'd just reduplicate them. If the implementation is more complicated, I've used templates to avoid code reduplication:

namespace
{
    //here T is intended to be either [int] or [const int]
    //basically you can also assert at compile-time 
    //whether the type is what it is supposed to be
    template <class T>
    T* do_foo(T* p)
    {
        return p; //suppose this is something more complicated than that
    }
}

int* foo(int* p)
{
    return do_foo(p);
}

const int* foo(const int* p)
{
    return do_foo(p);
}

int main()
{
    int* p = 0;
    const int* q = foo(p);  //non-const version
    foo(q);  //const version
}
UncleBens
Nice pattern. With the unnamed local namespace, it hides the semi-dangerous do_foo. I could do something like that in the privates of a template class as well. :)
Mordachai
This is the way to do it within existing implementation, but the problem is when you need to do casts in place of "something more complicated than that", as in the code example demonstrated. In our case, we need to cast to `const char*` if `T` is some `const U`, and to `char*` if `T` isn't const. And then you get into the mess of TMP to do that.
Pavel Minaev
Yes, in such a case you can use TMP classes like your `make_same_cv_as`. Or you could indeed implement one in terms of the other and cast away constness *once* (non-const to const doesn't seem to require a cast), seeing that there is a whole lot of casting going on in the first place.
UncleBens
A: 

From your example, this sounds like a special case of having a pass-through function, where you want the return type to exactly match the parameter's type. One possibility would be to use a template. eg:

template<typename T>  // T should be a (possibly const) ITEMIDLIST *
inline T GetNextItem(T pidl)
{
    return pidl
        ? reinterpret_cast<T>(reinterpret_cast<const BYTE *>(pidl) + pidl->mkid.cb)
        : NULL;
}
Laurence Gonsalves
This will break if `T` is not const because it'll try to cast off constness from a `const BYTE*` in the second (outer) `reinterpret_cast`.
Pavel Minaev
+2  A: 

You've got a few workarounds now...

Regarding best practices: Provide a const and a non-const versions. This is easiest to maintain and use (IMO). Provide them at the lowest levels so that it may propagate most easily. Don't make the clients cast, you're throwing implementation details, problems, and shortcomings on them. They should be able to use your classes without hacks.

I really don't know of an ideal solution... I think a keyword would ultimately be the easiest (I refuse to use a macro for it). If I need const and non-const versions (which is quite frequent), I just define it twice (as you do), and remember to keep them next to each other at all times.

Justin
A: 

You could use templates.

template<typename T, typename U>
inline T* GetNextItem(T* pidl)
{
    return pidl ? reinterpret_cast<T*>(reinterpret_cast<U*>(pidl) + pidl->mkid.cb) : NULL;
}

and use them like

ITEMDLIST* foo = GetNextItem<ITEMDLIST, BYTE>(bar);
const ITEMDLIST* constfoo = GetNextItem<const ITEMDLIST, const BYTE>(constbar);

or use some typedefs if you get fed up with typing.

If your function doesn't use a second type with the same changing constness, the compiler will deduce automatically which function to use and you can omit the template parameters.

But I think there may be a deeper problem hidden in the structure for ITEMDLIST. Is it possible to derive from ITEMDLIST? Almost forgot my win32 times... bad memories...

Edit: And you can, of course, always abuse the preprocessor. Thats what it's made for. Since you are already on win32, you can completly turn to the dark side, doesn't matter anymore ;-)

drhirsch
The above won't work, because template type parameter `U` is not in deducible context (it's not used in the function signature). So, to call `GetNextItem`, the client would have to write something like `GetNextItem<ITEMIDLIST, const ITEMIDLIST>(...)` - which is kinda defeating the point.
Pavel Minaev
Well, thats exactly what I have written. But its no complete defeat, because the code of the function doesn't need to be repeated. And it will work fine, if there is no second parameter.
drhirsch
+1  A: 

During my work I developed a solution similar to what Pavel Minaev proposed. However I use it a bit differently and I think it makes the thing much simpler.

First of all you will need two meta-functions: an identity and const adding. Both can be taken from Boost if you use it (boost::mpl::identity from Boost.MPL and boost::add_const from Boost.TypeTraits). They are however (especially in this limited case) so trivial that they can be defined without referring to Boost.


The definitions are following

template<typename T>
struct identity
{
    typedef T type;
};

and

template<typename T>
struct add_const
{
    typedef const T type;
};


Now having that generally you will provide a single implementation of a member function as a private (or protected if required somehow) static function which takes this as one of the parameters (in case of non-member function this is omitted).

That static function also has a template parameter being the meta-function for dealing with constness. Actual functions will the call this function specifying as the template argument either identity (non-const version) or add_const (const version).

Generally this will look like:

class MyClass
{
public:
    Type1* fun(
        Type2& arg)
    {
        return fun_impl<identity>(this, arg);
    }

    const Type1* fun(
        const Type2& arg) const
    {
        return fun_impl<add_const>(this, arg);
    }

private:
    template<template<typename Type> class Constness>
    static typename Constness<Type1*>::type fun_impl(
        typename Constness<MyClass*>::type p_this,
        typename Constness<Type2&>::type arg)
    {
        // Do the implementation using Constness each time constness
        // of the type differs.
    }
};

Note that this trick does not force you to have implementation in header file. Since fun_impl is private it should not be used outside of MyClass anyway. So you can move its definition to source file (leaving the declaration in the class to have access to class internals) and move fun definitions to source file as well.

This is only a bit more verbose however in case of longer non-trivial functions it pays off.


I think it is natural. After all you just said that you have to repeat the same algorithm (function implementation) for two different types (const one and non-const one). And that is what templates are for. For writing algorithms which work with any type satisfying some basic concepts.

Adam Badura
Yes, I can see how this pattern can be used to provide a const-correct single definition approach to the source code. Although it seems to me that the compiler is allowed to define as many actual implementations of fun_impl() as there are users of it with different CV needs (in this case two). I guess I have a similar reaction to this as to P.Minaev's TMP solution: These solutions require an exorbitant price in programming time to solve what seems to me to be an incredibly small nit-picky detail. Yes, casts should be avoided. Yes, const-correctness should be maintained. But at what cost?
Mordachai
I have, as I am sure many of you have, programmed in many languages. And C++ requires an awful lot of tedium to be shouldered by the programmer. His focus is taken away from solving the actual issue at hand, and forced instead to confront very, very nitpicky details of the language for rather modest gains in software-correctness. I'm all for improving software to mean what you say, say what you mean, and with the computer doing as much as possible to help us programmers to catch mistakes. But I notice the profound drop in productivity in C++ vs. [insert lang here] for many applications.
Mordachai
Yes. This will generate two versions of binary code (`const` and non-`const`) unless compiler does some incredibly clever optimizations (I doubt it does). But no. In my opinion it is not very complex. At least not more then any other function written to work well with two different types. I have programmed (and still am programming) in other languages. They do not have this problem but only because they do not have constness. In times like that it is simpler. But in times you have to return clones or proxies to prevent modification it is not as much fun anymore. Nothing is for free.
Adam Badura