C++11 and Boost - Succinct like Python
fendrich.se
fendrich.se
const map<const string, const tuple<int, int, StrToStr>> TagDataMap {
{"title" , make_tuple( 3, 30, stripnulls)},
{"artist" , make_tuple( 33, 30, stripnulls)},
...
it will be succinct when the whole type can get reduced to "auto". Python would just do: TagDataMap = {
'title': (3, 30, stripnulls),
'artist': (33, 30, stripnulls),
...
Ord implementation is also crazy: int i = static_cast<unsigned char>(s[0]);
return (boost::format("%1%") % i).str();
considering it doesn't even handle different string encodings.I don't want to complain about C++. It's a great language and has its uses. But it's not succinct and it's far away from what scripting languages provide. Claiming otherwise is close to fanboyism. Not being succinct is not a bad thing. There are many other things that C++ does for you that Python doesn't.
This is something that I think a lot of people miss about modern C++. They make the mistake of assuming you still program it using C idioms, when often you don't have to. They assume you have to "manually manage memory" when obvious and simple RAII techniques have basically obsoleted that for probably 90% of cases.
It's just not like that. If you have a problem that is "easy to express" in Python/Perl/Ruby/Javascript it is expressable in essentially the same way in C++.
The gotcha to my mind is more that the C++ environment is unforgiving -- a novice python programmer making a syntax error generally bangs around and gets it to work, maybe with some odd thought errors someone else will have to clean up. A novice C++ programmer is lost until they find an expert to explain the problem. So I don't think web engineers need to worry about losing their jobs to C++ hackers quite yet.
But at the same time, the intuition that C++ is "hard" is mostly wrong. A true expert can solve the same problems the scripting languages do, arriving at essentially equivalent code, in very similar time and with very similar (potentially higher due to typesafety) quality.
That's true, but I think ignores the main problem with C++. I'd say I know about three times as many things about how C++ as I do about how Python works, but I still feel like I'm more of an expert in Python than C++ just because C++ is such a stupendously large language now.
Nonetheless, the really are "true experts" in the world. These people tend to write most of the really important software, and they tend to work with each other on teams where "everyone" (or nearly so) is another expert. In those environments, I think a very strong case can be made for C++ as the best overall choice.
The real point is that simplistic arguments like "C++ isn't as clean as python" are off target.
return to_string(static_cast<unsigned char>(s[0]));The difference between make_tuple(...) and (...) is not significant compared to what it would have been before without list initializers at all and having to manually populate the map. That's something people who knew C++ of the past but not C++11 would be happy to see. The rest of the code is a great demonstration of these new features as well. That's the point: to demonstrate C++11, not undermine Python.
The article ends with some cogent criticism of C++. If this sets of your fanboy detector, you should turn it down a little.
That's not what he wrote though. The title is "C++11 and Boost - Succinct Like Python", the contents say "almost as painless as in a modern dynamic language like Python". That's what I can't agree with. There's still lots of supporting syntax that doesn't actually do anything for the end result.
So for correct programs the end result might be the same in terms of values. For buggy code and runtime speed that's not necessarily the case.
Nobody would argue that C++11 code can be much cleaner than old C++ code. But to say it's almost as clean as Python goes to far, and isn't fooling anybody. So why not be satisfied beating old C++?
I cannot put up with type systems that don't have complete or near-complete type inference. I don't know why one would start a new project in a language that didn't support Hindley-Milner.
In Haskell this would look like:
TagDataMap = [
("title", ((3, 30, stripnulls)),
("artist", ((33, 30, stripnulls)),
...
Haskell will correctly infer that TagDataMap :: [(String, (Integer, Integer, String -> String)].Ok, technically this is an associative list, not a Python dictionary, but it is a map and can be accessed like one. Hell, most people use dictionaries with less than 10 items, which are much slower than arrays most of the time
vector<int> items = {1,2,3,4};
int[] items = {1,2,3,4};
These have different types, so how would C++ know which one you meant if you instead wrote: auto items = {1,2,3,4};
Again, in Haskell this isn't a problem because literals have essentially a single type. (Edge cases around integers and strings notwithstanding).Edit: Just to clarify, in a Hindley-Milner system you could maybe get away with something like that, but everything you name in an HM system you must use, and that isn't the case in C++:
void foo() {
auto items = {1,2,3,4};
return;
}
I can then make two classes with list constructors: struct FooClass {
FooClass(std::initializer_list<int> list) {
cout << "Made a Foo!" << endl;
}
};
struct BarClass {
BarClass(std::initializer_list<int> list) {
format_your_hard_disk();
}
};
Obviously there are consequences to choosing the right type, but the type of that value never leaks out of the function. Nevertheless, because side-effects can happen anywhere, even in a constructor, C++ cannot optimize that out.This might be a convoluted example, and it may be flawed, but conjuring up others is not hard and demonstrates that C++ simply cannot ever have true HM type inferencing. Since the "real deal" is not possible, the language is complex and the standard is large, I would not expect to be able to live without annotations in C++-land. (Again, the FQA makes the horror of multiple non-orthogonal solutions to the same problems quite clear).
C++ could also type its list initializers with some polymorphic type (similar to Haskell's Num) but didn't do so.
This is not inherent.
There are no polymorphic literals in ML, just polymorphic math operators, which is enough of a blight on the standard that OCaml discarded it and forces you to use different operators for real and integer arithmetic. And there's only one kind of string in both MLs.
Haskell's Num hierarchy is troublesome. They traded usability for + with complexity for /. It's extremely unlikely that you could write a program in Haskell that does much arithmetic and have it build correctly on the first try without any manifest typing. This is one reason students of Haskell find things so confusing: type declarations are necessary at the top level simply because the extensions and complexity of modern Haskell break HM if you try using it everywhere. Also, the class system in there is not especially mathematically correct, which leads to the numerous replacement Preludes that try to do a better job but haven't caught on.
Strings are edge cases because they are not polymorphic unless you enable OverloadedStrings. Once you do, you will either replace the built-in string with something else (ByteString or Text) or find yourself in the same kind of trouble you'd be in with Num.
Let me be clear: I'm not saying that these problems are showstoppers. They're really minor annoyances once you're experienced, though they contribute to confusion for beginners. The point I'm trying to make is that you can't just drop HM into any old language and expect it to work. A greater point would perhaps be that all languages have warts simply because they're large, complex beasts (Haskell and C++ especially) and it's unproductive to point to a missing feature in one and demand some sort of perfected version of the other's.
Do you really believe Num overloading is a counterintuitive mess? I disagree completely.
> OCaml discarded it and forces you to use different operators for real and integer arithmetic
Which is pretty terrible.
> Haskell's Num hierarchy is troublesome.
Yes, but that's an orthogonal issue.
> This is one reason students of Haskell find things so confusing: type declarations are necessary at the top level simply because the extensions and complexity of modern Haskell break HM if you try using it everywhere
That sounds like FUD to me, a heavy Haskell user. Type declarations at the top-level are generally necessary to avoid the dreaded MR and for documentation purposes. Modern Haskell doesn't heavily use extensions that require type annotations on the top-level.
> Strings are edge cases because they are not polymorphic unless you enable OverloadedStrings. Once you do, you will either replace the built-in string with something else (ByteString or Text) or find yourself in the same kind of trouble you'd be in with Num.
It lets me use literals for Lazy Text, Strict Text, and String with the same syntax, which is nice.
My point is merely that giving a type to a polymorphic initializer is possible, and C++ chose not to.
https://github.com/Peaker/bottle/blob/master/codeedit/Editor...
I removed all the top-level type declarations, and only one definition broke, because of the MR.
showP :: Show a => a -> String
showP = parenify . show
Once I removed the type declaration, I made it work again by adding a parameter to avoid the MR: showP x = parenify (show x)
and everything compiles smoothly.Feel free to browse the bottle repo -- and try to build it without top-level declaration. Apart from a few functions in the entire project that use Rank2, you won't need any declarations.
This is very good code.
One difference I see between our styles that may explain the differences in behavior we see is that you're quite meticulous about importing only the parts of modules you need, and you make heavy use of qualified imports. My style has been to import everything in case I need it later and only use qualified imports when absolutely necessary, and it must be creating the unnecessary ambiguity that I have to deal with. I will try to adopt your style and see if it cleans up my error messages, and I'll encourage my friends to do the same.
void foo() {
auto items = {1,2,3,4};
return;
}
is the case of poorly designed syntax, in a truly Hindley-Milner system there is
no expression which doesn't have a type. Now, we could add some syntax that
screws that up, say: let v = I-HAVE-AN-AMBIGUOUS-TYPE in 0
But that's rather silly, isn't it? If a value isn't used then I would,
personally, like my language to optimize it away. So why not ditch such silly
syntax?This is part of why I said that the foundations of C++ are too far-gone.
And this syntax isn't necessary to save on typing. In a language that supported syntactic macros (such as Scheme or Racket) we could write something like:
auto items = my-vector-syntax(1,2,3,4);
which expands to something like: auto items;
items.push_back(1);
items.push_back(2);
items.push_back(3);
items.push_back(4);
If you're interested in true syntactic macros for non-sexp languages (though I
do suggest getting over the parentheses, my color settings make them nearly
indistinguishable from the background) look at Rust [1].Actually, Rust also has type inference [2].
Hell, stop programming in C++ and start using Rust! [3]
[1] http://dl.rust-lang.org/doc/tutorial-macros.html [2] http://dl.rust-lang.org/doc/0.4/rust.html#type-system [3] http://www.rust-lang.org/
I suggest using a HM language when you start a new project.
You might end up in trouble if you did something like
auto v = vec(
vec(1.2, 3.4),
vec(0)
)
not sure how well other languages deal with that.Looking at the complete picture makes a language with local type inference (like C++11) more or less as verbose as one with complete type inference.
But with type inference your tools can do that for you (e.g. C-u C-c C-t in haskell-mode).
_ f(_ a, _ b, _ c) { YOUR; CODE; HERE; }
?
Try to remember that Hindley-Milner is very hard to do outside of functional languages like ML and Haskell.
Furthermore, I assert that this truly is the wrong foundation. For new projects that must have OO, Scala provides local type inference and object orientation. If you're really hurting for some manual memory management, look at Rust [3] or Habit [2].
If we can recover the features we love on a new foundation that provides new features like type inference or memory-safety, then we've found a better foundation, IMHO.
[1] http://www.cs.ucla.edu/~palsberg/typeflow.html
You could say the same thing about everything old C++ compelled you to type. Or old COBOL, for that matter.
I'm more interested in what the C++ memory allocation style does to verbosity. Memory-handling styles are often more important than the traditional paradigms (functional, OO, etc.) in determining what's reasonable/pleasant to do in a given language.
const map<const string, const tuple<int, int, StrToStr>> TagDataMap {
{"title" , make_tuple( 3, 30, stripnulls)},
Why can't the compiler figure out the types involved in this map structure itself? The user-defined functions are declared above, make_tuple will be declared in some library, and the others are string/int literals.I don't think C++ can figure out the map<..> part simply because lots of things could have list initializers that accept lists of 2 item lists. C++'s overloading and implicit conversion conflicts with perfect type inferencing. This is an example of the kind of thing the FQA talks about, where several of C++'s issues collide to produce counter-intuitive behavior.
I do wonder if you could get away with this:
const map<const auto, const auto> TagDataMap { ...
I don't have access to a C++11 compiler from where I am to find out though. I am particularly unclear on the interaction between `auto` and other aspects of a type declaration--I don't know if you can nest `auto` like this deep inside some other type declaration. I don't see why you couldn't, but wouldn't be shocked either. const auto TagDataMap = map_initializer( { "foo", { 1, 20, func }, ... } );
where map_initializer would be a template function that inspects the typedefs of its std::initializer_list<T> argument and generates an appropriate map.In real C++ code, this would almost never be what you actually want, though, as the types in an initializer list in C++ do not imply the types being initialized, they are parameters to a constructor to be determined elsewhere.
string ord(string const &s) {
return to_string(unsigned(s[0]));
}
He skips all the const-refs...The C-style cast is shorter, but less safe, so you should not use it: http://stackoverflow.com/questions/1609163/what-is-the-diffe...
Yeah, I couldn't quite decide if I should include const refs. On the one hand, it is idiomatic, but on the other hand I didn't want to clutter the code with something that is just an optimization on paper.
btw. Please fix either the size of your code or the margins of your blog. You use only half the screen width but I still have to scroll sideways in the code listing.
http://play.golang.org/p/53jSv32wSF
(You can't access the filesystem on the playground, so it doesn't run.)
* Look at that error handling. Mmmmhmmm, clear and explicit. If something goes wrong, I'll know about it.
* Apart from, of course, errors that I ignore, such as when converting the year from 4 chars to an int (line 54). I don't care if I can't parse that.
* Note the difference (lines 54, 56) between parsing the track (which is a byte), and the year, written as 4 digits. The byte can just be cast to an int, the string needs to be parsed by the strconv package.
* I have a little bit of defensive programming in the form of a panic(), which would tell me if something that I thought was impossible has happened.
* defer on line 19 makes sure the file closes if we managed to open it, regardless of when we return.
* type casts are explicit, even from int to int64 (line 20)
* I don't specify which interfaces a type implements, the compiler handles that all for me.
* Parse() returns map[string]interface{}, which allows me to store anything (in this case just ints and strings) in a key-value store.
* A type-safe printf! "%v" (line 67) uses reflection to look at the type of the argument and deduce the natural format. So I can pass it an int or a string and it works :)
* On line 86 I pass this weird new type to a printf function and it goes ahead and uses the String() method that we defined. If we hadn't defined that we'd get a printout of the type, which in this case would be the 128 bytes we read.
Line 24 makes a byte slice, line 25 populates it with data read from the file.
if err != nil {
return nil, err
}But Python's major strength is readability, even more than coinciseness, or better, to provide both at the same time.
C was born as a terse language, sacrificing readability for coinciseness (the original examples in K&R are incredibly succint, almost elegant, but far from readable). I can't see many improvements in C++ (a language that arguably has worse coinciseness than C, without gaining in readability in the process)in that regards, even with the new version.
I work with C/C++ and I could easily understand a Python script even when I wasn't that familiar with the language, the same cannot be really said for that code in C++.
BTW, I'm not bashing C++, that has a lot going for it,just not readability and coinciseness.
Broadly: reading code is just hard. It's much harder than writing code. There are no non-trivial codebases that can be considered "easy to read" (if you think there are, then you're fooling yourself), and the choice of language makes only mild difference.
(edit: I'm amused at the multiple people who had to jump in to explain "decorators aren't hard!" instead of responding to my core point. First, I know what a decorator is. Second, no one who isn't a reasonably proficient python programmer is going to have any clue what @classmethod does or why all the old code doesn't break without using it. It's non-standard, language-specific syntax in exactly the same way that an STL iterator is, and the only reason you think one is easy and not the other is because you know Python and not C++.)
But a complex program written in C++ (or using advanced C++ constructs) will scale much worse than a Python one, in my opinion.
See for yourself: https://gist.github.com/3988350
def __init__(self, x, y, ..):
self.x = x
self.y = y
Is so tedious and DRY-violating. def Circle(center, radius):
circumference = 2 * pi * radius
return object:
to getCircumference():
return circumference
to ...
The "self" is the lexical scope. Simple and more effective than Python's method, IMO.Even now, I still like Haskell's
[fn(x) | x <- myList, x < 5]
over Python's equivalent [fn(x) for x in myList if x < 5]
Of course, I'm a mathematician, so that probably explains everything about my convoluted path of understanding these things, and my preferences to this day. :-)Decorators are pretty opaque; idiomatic python makes minimal use of them, and uses them only for things that don't change the meaning of the method inside. I've never used @classmethod and hope to never have to. I don't think it's fair to compare to an STL iterator, which is something you're encouraged to use as much as possible.
I don't know what you're talking about for iterables; aren't they obvious?
It is sufficient if the C++ code I write is reasonably readable to a fellow C++ programmer. I really don't care if it is not readable to a casual observer.
It is nice that Python code is readable to even to non python people. But it is not a requirement for every language.
I recently rewrote one of my C++ projects(which uses C++11 features and Boost.asio) in Go. It took me half the time, less than 2/3 lines of code compared to the C++ version. This is expected. But most surprisingly, despite that I tried my best thinking about how to make it as efficient as possible in every detail when writing C++ version, and that I'm quite new to Go, the Go version still achieves nearly 100% higher throughput than C++ version.
Maybe my C++ skill sucks. But I guess I'll just let it suck.
Check this out: http://en.munknex.net/2011/12/golang-goroutines-performance....
That the language may have some Python-esque tendencies (given very particular language, compiler and library versions, and a depth of knowledge not required in the Python equivalent) doesn't seem that interesting. Especially when you consider that 9/10 C++ programmers you meet don't actually code in that style and don't intend to.
cout << join(pm | transformed([](propMap::value_type pv){...
I'm sorry, but as "succinct" as it is, this is basically an unreadable bullshit. Reminds me strongly of a macro abuse in C.Tuples in python are a reasonable tradeoff between not wanting to declare anything and the hassle of anonymous structure. This doesn't apply C++ where the equivalent is the POD struct:
//In the olden days we could not initialize a map inline like this
//Key -> metadata mapping. Unfortunately strutcts cannot be declared
//inside a template declaration.
struct TagData { int start; int length; StrToStr mapfun; };
const map<const string, const TagData> TagDataMap {
{"title" , { 3, 30, stripnulls}},
{"artist" , { 33, 30, stripnulls}},
{"album" , { 63, 30, stripnulls}},
{"year" , { 93, 4, stripnulls}},
{"comment" , { 97, 29, stripnulls}},
{"genre" , {127, 1, ord}}};
Creating a named struct pays off when it's time to use the Map, no extra locals or tie() needed to write clear code: //for loops over collections are finally convenient to use.
for(auto td : TagDataMap){
//C++ created a horrible precedent by making the data type of
//a map pair<K,V> instead of struct ValueType { K key; V value; };
auto tdd = td.second;
ret[td.first] = tdd.mapfun(sbuf.substr(tdd.start, tdd.length));
}
http://liveworkspace.org/code/bcd52515fb7161858e974b7ff3c0aa...Perhaps I should have mentioned that, though.
That said, I guess we have to live with C++03 for many more years. E.g. in one application that I co-developed, we use a library that can currently only be compiled without a substantial amount of effort on Visual Studio 2005 or 2008. The DLL compiled with older versions is not compatible with 2010 or 2012. So, we are basically stuck in 2005 or 2008 land for now.
To me, this post would have been 10X more interesting if there was some elementary benchmarking included. If it's about the same speed as the corresponding Python code, which I'd bet is easier to write for more programmers, then I don't quite see the point being as clearly proven.
If it's 100 times faster (or whatever), then it gives more credibility to the idea of writing code like this in C++ to begin with, and to learning all the new language features that makes it safe while being that much faster than Python.
The program is I/O-bound, so the only speed improvement comes from not having to start up the Python interpreter.
If the task was CPU bound, you would get a great performance boost (sometimes 100x over CPython), but that is well known.
There can be other reasons than performance for writing in C++. Used well, the strong static type system can catch many bugs. I suspect (but I cannot prove) that it could be almost as good as Haskell.
I use Python for most things, though, so I don't really advocating switching to C++ for every little scripting task.
For other reasons to write in C++ (or any static typed lang), I'd add type dispatching: calling different methods based on type. I also hate having a typo in a method name throwing at runtime.
I also often convert python implementations to C++, often using boost, usually for performance in one part of the code. You can quite often get the same implementation in a similar number of lines, especially since C++11 with boost.
#include <iostream>
#include <vector>
int main() {
std::vector<std::string> v;
v.push_back(std::string("Hello"));
v.push_back(std::string("there"));
for (auto ii = v.begin(), ie = v.end(); ii != ie; ++ii) {
v.clear();
std::cout << *ii << std::endl;
}
return 0;
}
Preventing this sort of thing requires strong guarantees about aliasing (to ensure that "v" can't alias the vector being iterated over), which the C++ type system can't help you with. #include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>
using namespace std;
template<class T>
void print(const T& v) {
copy(begin(v), end(v), ostream_iterator<string>(cout, "\n"));
}
int main(int argc, char** argv) {
vector<string> v{"Hello", "there"};
print(v);
}
Also, Haskell is only so safe—at work we’ve taken to rejecting non-total functions in review, because they cause more hassle than they’re worth. By non-total, I refer more to “error” than non-termination, of course. #include <iostream>
#include <vector>
template<typename T,typename U>
void print(const T& v, U& w) {
for (auto ii = v.begin(), ie = v.end(); ii != ie; ++ii) {
w.clear();
std::cout << *ii << std::endl;
}
}
int main() {
std::vector<std::string> v;
v.push_back(std::string("Hello"));
v.push_back(std::string("there"));
print(v, v);
return 0;
}
In general there are lots of ways to get non-const access to const data. You'd need a sophisticated form of whole-program alias analysis to eliminate the unsafety here. (Even if you didn't have the second "U& w" parameter, there are other potential ways that "print" could get non-const access to "v"; global variables, TLS, by accessing a member of "v", etc.)For example:
#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>
class mystring {
public:
std::string m_s;
mystring(const std::string &s) : m_s(s) {}
};
std::vector<mystring> v;
void operator<<(std::ostream &out, const mystring &ms) {
v.clear();
out << ms.m_s;
}
int main() {
v.push_back(mystring(std::string("Hello")));
v.push_back(mystring(std::string("world")));
std::copy(v.begin(), v.end(), std::ostream_iterator<mystring>(std::cout, "\n"));
return 0;
}You will still need C++ for dealing with all the legacy C++ out there, but if you are starting a new project, you ought to be able to get the small, fast, efficient runtime advantages of a legacy monster like C++ from a much smaller, simpler language with a modern standard library.
Maybe it will be Go, but even if not, we need a simple, safe, productive language with modern features pre-installed (unicode strings, safe arrays, lists, maps) and a modern standard library that statically compiles to small, fast, native executables.
C++ with its "if you use the most recent 10% and pretend the old 90% doesn't exist, it's a great language" ethos is not what we need for new code.
std::string type = "foo";
auto it = std::find_if(
channel_widgets.begin(),
channel_widgets.end(),
[type](const std::shared_ptr<Widget> &w){
return (w->getType() == type);
}
);
if (it == channel_widgets.end()) {
std::cerr << "widget not found" << std::endl;
} else {
std::shared_ptr<Widget> target_widget = *it;
std::cout << "widget found." << std::endl;
}
versus (for example): type = "foo"
matches = [x for x in widgets if x.get_type() == type];
if matches:
target_widget = matches[0]
print "widget found:",target_widget
else:
print "widget not found"
I may be missing out on some C++11 feature for doing this better. (Or even some Python feature for doing this better!) type = "foo"
try:
target_widget = (x for x in widgets if x.get_type() == type).next()
print "widget found:",target_widget
except StopIteration:
print "widget not found" std::string type("foo");
auto range = channel_widgets | filter([type](const std::shared_ptr<Widget> &w) {
return w->getType() == type;
});
if (range) {
std::string target_widget = *range.begin();
std::cout << "widget found." << std::endl;
} else {
std::cerr << "widget not found" << std::endl;
}First write a function that searches an entire range with a predicate, so you don't have to write the .begin() and .end() anymore. That's just applying DRY. Then you could write a helper struct that wraps an iterator and the end iterator of the range searched, so you can give it an operator bool (). Return this struct from your find function and you get code like this:
auto result = my::find_if( channel_widgets, predicate );
if( result )
do_stuf_with_result( result.it );
else
error(); auto it = channel_widgets.begin();
while (it != channel_widgets.end())
if (it->getType() == type) goto found;
// error
found: // whatever
I kinda hate myself every time I write such for-loop, but it's still more succint than any variant of find_if. Generally, I use "trivial" STL algorithms (find, find_if, for_each, ...) only if I can reuse the functor several times. Otherwise it's not worth the hassle.PS: you can do it also without goto... use "break" after if, and after while you write
if (it == channel_widgets.end())
whatever; // not_foundThere's also a ton of redundant libraries and language features, resulting in a lot of decisions being arbitrary choices between equivalents. Having the same decision made throughout the code makes everyone who has to read the code's life easier.
C++ can be made to do lots of things, and it doesn't achieve that by being elegant. It achieves that by trying to do everything possible under the sun. Because of that, C++ is and always will be much more complex than the other languages.
If your environment is solely your own and you don't care about having your users build something, fine.
I think only a hardcore C++ developer would claim that the author's sample is "succinct". Honestly, C++11 is still far behind the easiness of Python (or Scheme), even with Boost.
Funny, a decade ago Ada 95 (the "military" language for high-critical applications) looked like a monstrous over-designed beast when compared to C++. Today Ada 2012 looks elegant and even "small" when compared to C++11. How times have changed :-)
* Horrible error messages
* Even simple programs take ages to compile due to massive header files.
LLVM/Clang help on both fronts but it's still quite difficult.
D2 seems much more promising if you can do without the libraries.
Could you expand on this a bit? I'm not a C++ guy, but I was thinking of picking it up. By what metric do you mean it has "lost credibility?"
Many desktop and server applications are still written in C or C++, including the Web browser I'm writing this on. Reports of their deaths are exaggerations.
I'll take that over a tutorial any day.
string s = "string";
reverse(s.begin(), s.end());
And here's how to reverse a string in Python:
s = "string"
print s[::-1]
In my opinion, the C++ version is far easier to read and just makes more sense. Edit - This is just one small example, however, I find it holds true for the entire languages in general. Also, I do a lot of Python and C++ systems programming so I use both frequently and while I prefer C++, I think Python is the best scripting language available today.
s = "string"
reversed(s)
It also has the plus of creating a generator, saving memory from a new array allocation.It's odd how Python has list.reverse() but not str.reverse(). Probably a conscious design decision sometime ago.
In terms of readability, they both beg the question - is the reversed string ever actually held in memory at some point or can it occur lazily via latent reverse iteration?
I mean is the alleged readability of either language really answering any important questions?
In this case, we are accessing the position "two colons minus one". I refuse to believe this makes any sort of sense for someone that has no deep knowledge of Python.
Your criticism is analogous to me looking at "int* foo = bar()" and saying, hey, we're multiplying a type by a name! How ridiculous!
''.join(reversed(s))
It's still not the same as your example, because in C++ you reverse the string in-place. If you have a mutable array-like, you can do arrayish[:] = reversed(arrayish)
But to me the syntax with `s[::-1]` is already perfectly clear. There is the difference that `reversed` returns an iterator, while `s[::-1]` constructs a new string.using namespace boost::adaptors;
s | reversed
or reverse(s)