Sunday, May 11, 2014

C++11: better, but still frustrating


Update: jduck pointed out that the before/after code snippets were identical. Oops. Now fixed.

I'd previously given up on C++ due to the many small frustrations: incomprehensible error messages, silly parsing issues (e.g. '>>'), rules to avoid subtle errors, and many other small frustrations that soured me on the language. That was back in the days of C++98 and C++03.

The language has evolved, and recently I found myself working on a project written in C++11. So far my experience has been better, but still frustrating.

A Motivating Example


I'll start with a real example. The project created a lot of Foo objects that were passed by reference to numerous functions. I needed to keep a collection of every Foo object that was passed to a specific function.

My first thought was, "I know, I'll create a vector of Foo&". This thought is simple, elegant, and of course, wrong.

A vector of references isn't possible because references can't be reassigned. That is, references[0] = foo; would update the referenced object, not the zeroth entry of the references vector. More technically, references are not CopyAssignable, a requirement for members of containers.

Errors Galore


But how would someone new to C++ know this? What do compilers say when making a vector of references? Lets find out by compiling this small (and wrong) program.

#include <vector>
#include <iostream>

int main(int argc, const char* argv[])
{
    int a = 1;
    std::vector<int&> test = {a};
    std::cout << "a: " << test[0] << std::endl;
    return 0;
}

Here are the results for Clang, GCC and MSVC:

Compiler Error List Error Count
Clang rextester.com/JMFGP72087 158 lines
GCC rextester.com/HGKFIT84222 187 lines
MSVC rextester.com/UXPG39365 107 lines

In classic C++ style, the error messages are hundreds of error lines from obscure library implementation code. They give no indication of what is wrong, and no indication of the solution. I pity someone who doesn't have C++ experience trying to figure out what is wrong with their code. Pretty much any error would be more helpful, even an obscure message like "Member must be CopyAssignable" -- as long as it pointed out the correct line of code.

The Fix


For reference, the corrected program is:

#include <vector>
#include <iostream>
#include <functional>

int main(int argc, const char* argv[])
{
    int a = 1;
    std::vector<std::reference_wrapper<int>> test = {a};
    std::cout << "a: " << test[0] << std::endl;
    return 0;
}

The fix is to use the std::reference_wrapper utility function when making a container of references.

Conclusion


There's definitely upsides: the '>>' parse has finally been fixed. Classes can now be initialized with initializer lists. There is type inference via 'auto'. For-each style loops exist.

C++11 is a great improvement over C++03, but its still frustrating: the obvious solution (like containers of references) is wrong in subtle ways, and compilers still generate hundreds of obscure error messages for a one-character typo.

1 comment:

  1. Your first code snippet is incorrect (it's identical to the second).

    ReplyDelete