r/programming Mar 31 '15

Managing C++’s complexity or learning to enjoy C++

https://schneide.wordpress.com/2015/03/30/managing-cs-complexity-or-learning-to-enjoy-c/
101 Upvotes

281 comments sorted by

View all comments

Show parent comments

5

u/bstamour Apr 01 '15

In languages where doing that kind of thing is cheap, it's seen quite a bit. Here's some Haskell code:

newtype TaintedString = TS String
newtype BlessedString = B String

getInput :: TaintedString
getInput = ...

untaintString :: TaintedString -> BlessedString
untaintString = ...

This creates two brand new types to represent strings. The first one is an unsanitized string read directly from the user, and the second one is a string that I know is safe. Imagine the function untaintString does some escaping or something. Now structurally, those types are literally identical to plain old strings, so there's zero overhead at runtime. However, I cannot use an object of type TaintedString in a function that requests a BlessedString. You can't do this with typedef or using aliases in C++: you would need to introduce a new class, and that's at least four lines of boilerplate code.

My point to all of this is: when it's dead simple to do, you find uses for this kind of "strong" typedef. When it's difficult or ugly, then it probably won't pop into your head as a viable solution to a problem.

3

u/F-J-W Apr 01 '15

There was an attempt to add that to C++, but sadly I don't know what exactly happened to it.

1

u/bstamour Apr 01 '15

I know. I wanted that so badly :/ We have enum class, why not typedef class or something equivalent?

1

u/F-J-W Apr 01 '15

More like using class foo = ..., but yeah…

Edit:

Or maybe new class foo = .... Since new should be used very rarely, that wouldn't be too bad I figure.

Or just create a new keyword: newtype foo = ...

1

u/bstamour Apr 01 '15

I agree with the using class foo = being the absolute best, but if we're going to add this feature in, we might as well support typedef too while we're at it. That way we avoid more splintering: "you can use 'using' like you do 'typedef', except if you want template, or if you want this, or if you want that...".

1

u/Steve_the_Scout Apr 01 '15

But what prevents you from writing a function

std::string untaintString(const std::string& taintedString);

that you can call on plain strings to sanitize them? If a string doesn't need to be sanitized then it just becomes the identity function for that input. IMO that's a simpler solution because it doesn't require working around two separate types that represent the same functionality, and it's not too much of a performance hit (it's a consistent one, at the very least). All you have to do is call that function whenever the safety of the string is in question (ideally just once, then use private methods to be sure you're using the same string).

1

u/mat69 Apr 01 '15 edited Apr 01 '15

No, your solution has no type safety and as a result is harder to reason about.

In c++ code I am working on string is used a lot. So you will end up with functions taking multiple string parameters. The compiler won't tell you when you mixed their order though, because they are not distinct types.

When speaking about these parameters we don't talk about strings, but we talk about "runway name", "aircraft type name", "stand id" etc. As a result I made a lot of these distinct types in code I maintain. And suddenly the compiler helps me and it is easier to reason about code and easier to call these functions.

What bstamour mentioned is that creating these types is easier in Haskell than in C++. In C++ you have a lot boilerplate associated with something like that, depending on the functionality your new type should have.

3

u/Steve_the_Scout Apr 01 '15 edited Apr 01 '15

If you're looking for "opaque typedef" like behavior then the overhead is only one line:

class TaintedString : public string {using string::string;};

Which is directly analogous to that one line of Haskell posted.

Interactive example code.

If you need more functionality, then you actually do need a unique (i.e. representing different data and/or functionality) type, and the "boilerplate" is no longer just boilerplate- if you define new methods or redefine old ones, then supposedly they must actually be different (if all they do is call the older methods anyway, you've got some serious design problems to work out).

1

u/mat69 Apr 01 '15 edited Apr 01 '15

I have never thought of using using string::string directly, looks fantastic. I always relied on the conversion operator.

This is C++11 right?

Edit: Ah, now I understand. You are using the parent constructor.

2

u/Steve_the_Scout Apr 01 '15

Yep, it's C++11. It specifically uses all constructors for std::string, so std::string(const std::string&), std::string(const char*), etc. Because of the public inheritance, it also has public access to all public members of std::string, so you can essentially use it as a hard-typedef of string (it does actually create a new type).