If you have been using C++ for some years, you will know that there is more than one way to do anything in C++. This also means you have more than one way to get a compilation failure in C++. This blog post is quite ambitious, as I want to do an exposition on how to avoid C++ compilation errors. But to understand how to avoid C++ compilation errors, you need to understand where they come from.
Yes, it does come from the compiler. But what triggered it in the first place is something you – the programmer – did. You gave a C++ program that the compiler couldn’t transform to machine code and thus resulted in a compilation failure.
Don’t talk in euphemisms you say? Let me give a canonical example of a compilation failure:
void ConvertStringToPasswordForm(char password[])
This function’s driver is given below:
int main(int argc, char** argv)
The error message is clear:
error C2440: ‘initializing’: cannot convert from ‘const char [39]’ to ‘char *’
Aha, it’s a const problem you wonder. You change the driver to:
int main(int argc, char** argv)
But the compiler is adamant. It still did not resolve the compilation failure. Now the cpp compilation error message is:
error C2664: ‘void ConvertStringToPasswordForm(char [])’: cannot convert argument 1 from ‘const char *’ to ‘char []’
For a while, you consider changing the first function’s parameter to const char* to fix the compilation failure but decide against it. As a wonderous C++ programmer, you decide to shut up the compiler:
int main(int argc, char** argv) < const char* password = "MyTopSecretPasswordPublishedInABlog:-)"; ConvertStringToPasswordForm(const_cast(password)); std::cout
Does the program compile? Yes! Does it work? NO!
What is the right way to fix this cpp compilation issue? Here it is:
int main(int argc, char** argv)
It is not enough to pacify the compiler; you need to understand why a program works the way it works. (Try the set of programs here: https://coliru.stacked-crooked.com/a/5e246877801d5263 ).
1. Understand the language well. Arrays decay to pointers in both C and C++, but not always as can be seen from the above example. One of the most important ways to avoid compilation failures in any language is to understand the language well.
2. Understand that Language grammars change too. Let me explain this with an example:
int main(int argc, char *argv[]) < for (int i = 0; i < 10; ++i) < /*do something */ >int valueof_i = i; return 0; >
I hoped this would work on an old Dev-C++ IDE. It did not. I got the following C++ compilation errors:
The program would be valid because, in prehistoric times, the scope of index variable for a for loop extended outside. So, if you are migrating an old C++ program before the new ISO ‘for’ scoping was introduced, you will run into CPP compilation issues. In Visual Studio you can turn off conformance (not recommended for new code):
3. IDEs are your friend. Today there is no dearth of good IDEs, both free and paid. Let me give you an example of where IDE helps:
As soon as I put the opening braces, my trusted Visual Studio 2019 IDE helpfully completes:
How many times have you forgotten to put that in that semicolon and resulted in a compilation failure? Isn’t it great that IDE can fill that in? Additionally, a good IDE gives keyword highlighting, IntelliSense, and context-sensitive help.
4. Use good engineering practices. Assume you are working on a complex program. Use either the bottom-up or top-down approaches ( https://en.wikipedia.org/wiki/Top-down_and_bottom-up_design ) to code it. Write small functions that are easy to understand and easier to compile. As you write the code, constantly make sure the program is compiling clean.
5. Be extra careful while writing templated code. I know template metaprogramming is Turing complete and it is fun to write metaprograms. I wonder if the compiler writers intended the template errors also to be Turing complete. ? But the recent compilers (those supporting C++17 std and above) are becoming better at pointing errors.
Let me give an example: Here is a code:
template class SimpleTemplateUse < private: const T& v; public: SimpleTemplateUse(const T& v) :v(v) <>>; template int f(T x) < f(SimpleTemplateUse(x)); >
Instantiating this template as f(0) gave me the following error in my old Dev-C++ IDE:
The same program in VS 2019 gave a sane:
fatal error C1202: recursive type or function dependency context too complex compilation failure error. Much better!
6. Understand the API of a third-party library before trying to use it. This advice is valid for all compiled programming languages that support third-party libraries, not just C++ for avoiding compilation errors. You don’t use the win32 API, without knowing where to use HINSTANCE, HANDLE, or HMODULE. Study the details before you delve into complex libraries.
7. If your program is intended to be cross-platform, make the platform-dependent code is well encapsulated under conditional compilations. I was going through the source code of folly recently ( https://github.com/facebook/folly ) and I see it to be beautifully written. The cross-platform parts are well encapsulated:
#ifndef _WIN32 #define _GNU_SOURCE 1 #include #endif
Found in folly\ClockGettimeWrappers.cpp. Because they support macOS, Linux and Windows it is important for them to use conditional compilation. If the program is cross-platform, it is better not to use any proprietary extensions to the C++ language given by different compiler vendors too.
8. Follow a good C++ coding standard. It is said by none other than the creator of C++ that “within C++ is a smaller safer language struggling to get out.” When you use a good coding standard like CPPCoreGuidelines ( https://github.com/isocpp/CppCoreGuidelines ) or Google coding style ( https://google.github.io/styleguide/cppguide.html ), you automatically confine yourselves to the smaller, safer language thereby reducing unnecessary compilations errors.
9. Understand today’s (depreciation) warnings shall turn errors tomorrow. Not only to make your software compilation future proof but also as a good engineering practice, enable all warnings during development and fix them religiously. Enough said.
10. Use containerization techniques like Docker to fix dependencies. One of the important use cases of Docker is to reproduce build environments starting from a base image. Of course, writing a good Dockerfile requires expertise, but it is mostly a one-time activity.
11. Use CMake or any such build generators to automate builds (read more about CMake generator). IDEs are good for software development but it is important to automate the builds without manual intervention. See the next point.
12. Set up a check-in/merge/rolling build policy. Make sure any merge to a branch is accompanied by an automated build that fails the merge if there are errors. This will ensure that errors that have sneaked into a branch do not propagate everywhere.
Well, I remind myself that am writing a blog post, not a book. This post is long enough that I need to give a concise summary.
Here’s a quick summary chart on how to avoid C++ compilation failure:
An expert software developer and product strategist, Dori Exterman has 20 years of experience in the software development industry. As CTO of Incredibuild, he directs the company's product strategy and is responsible for product vision, implementation, and technical partnerships. Before joining Incredibuild, Dori held a variety of technical and product development roles at software companies, with a focus on architecture, performance, advanced technologies, DevOps, release management and C++. He is an expert and frequent speaker on technological advancement in development tools.