To answer this question simply, C++ is a much more complex language than other languages available on the market. It has a legacy inclusion model that parses code multiple times, and its templated libraries are not optimized for compilation speed.
Grammar and ADL
Let's have a look at the grammatical complexity of C++ by considering a very simple example:
x*y;
While you’d be likely to say that the above is an expression with multiplication, this is not necessarily the case in C++. If x is a type, then the statement is, in fact, a pointer declaration. This means that C++ grammar is context-sensitive.
Here’s another example:
foo<x> a;
Again, you might think this is a declaration of the variable "a" of type foo, but it could also be interpreted as:
(foo < x) > a;
which would make it a comparison expression.
C++ has a feature called Argument Dependent Lookup (ADL). ADL establishes the rules that govern how the compiler looks up a name. Consider the following example:
namespace A{
struct Aa{};
void foo(Aa arg);
}
namespace B{
struct Bb{};
void foo(A::Aa arg, Bb arg2);
}
namespace C{
struct Cc{};
void foo(A::Aa arg, B::Bb arg2, C::Cc arg3);
}
foo(A::Aa{}, B::Bb{}, C::Cc{});
ADL rules state that we will be looking for the name "foo" considering all arguments of the function call. In this case, all of the functions named “foo” will be considered for overload resolution. This process might take time, especially if there are lots of function overloads. In a templated context, ADL rules become even more complicated.
#include
This command is something that might significantly influence compilation times. Depending on the type of file you include, the preprocessor might copy only a couple of lines of code, or it might copy thousands.
Furthermore, this command cannot be optimized by the compiler. You can copy different pieces of code that can be modified just before inclusion if the header file depends on macros.
There are some solutions to these issues. You can use precompiled headers, which are the compiler's internal representation of what was parsed in the header. This can’t be done without the user's effort, however, because precompiled headers assume that headers are not macro dependent.
The modules feature provides a language-level solution to this problem. It’s available from the C++20 release onward.
Templates
The compilation speed for templates is challenging. Each translation unit that uses templates needs to have them included, and the definitions of these templates need to be available. Some instantiations of templates end up in instantiations of other templates. In some extreme cases, template instantiation can consume lots of resources. A library that uses templates and that was not designed for compilation speed can become troublesome, as you can see in a comparison of metaprogramming libraries provided at this link: http://metaben.ch/. Their differences in compilation speed are significant.
If you want to understand why some metaprogramming libraries are better for compilation times than others, check out this video about the Rule of Chiel.
Conclusion
C++ is a slowly compiled language because compilation performance was not the highest priority when the language was initially developed. As a result, C++ ended up with features that might be effective during runtime, but are not necessarily effective during compile time.
P.S – I work at Incredibuild, a software development acceleration company specializing in accelerating C++ compilations, you are welcome to try it for free.
It takes significantly longer to compile a C++ file
- do you mean 2 seconds compared to 1 second? Certainly that is twice as long, but hardly significant. Or do you mean 10 minutes compared to 5 seconds? Please quantify. – Nick Gammon