Actually, in my recent C I've taken to wrapping most ints in one-member structs, so the compiler will catch it when I pass a foo id where I meant to pass a quantity...
Yes. Composition over inheritance (http://c2.com/cgi/wiki?CompositionInsteadOfInheritance). Oftentimes, these string-like aren't really strings, but have a string representation that dominates their other uses. For example, http headers are, AFAIK, (key,value) pairs that have the string representation "key: value". A good implementation should discriminated between such objects and their implementation.
If your language introduces needless friction when you try to use composition, I would blame the language before I blamed the string class.
I love this technique. It can catch so many errors. It's also easily extensible to slightly more complicated cases. For example, I was working on some gnarly code that was working with a bunch of times with different epochs (e.g. time since startup on the local computer, time since startup on a remote computer, and time since the UNIX epoch). Rather than try to remember what was what, or try to painfully encode it in variable names, I simply wrote a struct that contained the number of seconds and an enum indicating what epoch it used. Then any operation on a pair of times (deltas, comparisons) got factored into a function that asserted the time bases of the two times were compatible.
It's interesting how little use this seems to get in C in general.
It works just like one-member structs, in that it can be treated as a single value, passed and returned by value when calling functions, and keeps you from accidentally mixing different kinds of values. Having the "epoch" field along for the ride just means you can add some additional smarts.
So yes, like I was thinking; a tagged union - the epoch field determines the interpretation of the val field. The full application of "wrap values in single element structs for better static guarantees" would be to have a different time struct for every epoch. This is a (possibly quite useful) step back from that, since C's lack of polymorphism would mean a need to implement every time function for epoch even when the logic is the same.
I guess that makes sense now that you explain it. Conceptually, each epoch value results in a different type for the other field, meaning it works like a union, even though it's actually implemented using the same primitive type for each.
Actually, in my recent C I've taken to wrapping most ints in one-member structs, so the compiler will catch it when I pass a foo id where I meant to pass a quantity...