In order to compile C++ templates the compilers like Embarcadero RAD Studio C++ compilers are required to perform two stages: definition stage and instantiation stage.
The template definition stage
On this stage the following checks are performed:
- static assertions and availability of names declarations (such as names of classes and functions) independent on template parameters are checked, even if they are belongs to discarded branch of
if constexpr
statement (on details about compile-time if, please see If Statements in Modern C++); - syntax correctness is checked, including for unqualified names which are depends on template parameters.
The complete check of unqualified names is postponed until the template instantiation stage when the template arguments are known.
Please note, since the compilers like Embarcadero RAD Studio C++ performs many checks on this stage a programmer is often see general problems even before the template instantiation stage!
Example of errors even when the branch is discarded at compile-time:
1 2 3 4 5 6 7 8 9 10 11 |
void foo() { if constexpr (false) { // Provokes the definition stage // error if println is not declared println("Borland C++"); // Always provokes the definition stage error static_assert(false); } } |
Example of syntax error upon checking of unqualified names which are depends on template parameters:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
namespace util { struct Data {/* ... */}; std::ostream& operator<<(std::ostream&, const Data&); template<typename T> void println(T value) { std::cout << value << '\n'; } } // namespace util template<typename T> void out(T value) { // Syntax ok since println() template is declared println<T>(value); // Provokes syntax error (parsed as "log less-than // T ...") since log() template is not declared and // the compiler does not know that the less-than // token (<) is not less-than operator but the // beginning of a template argument list. log<T>(value); } void foo() { out(util::Data{123}); } |
Another example of syntax error upon checking the construct before period that depends on a template parameter:
1 2 3 4 5 6 7 8 9 10 11 |
struct Data { template<typename T> T as() const {/* ... */} }; template<typename T, typename D> T as(const D& data) { // Provokes syntax error since .template construct // before "as<T>" is required. // Must be: return data.template as<T>(); return data.as<T>(); } |
The template instantiation stage
The first point where the template is used for a first time for particular template arguments is called the point of instantiation. At this point the template arguments are known hence the compilers are able to generate specializations of templates for concrete arguments. During the generation of a specialization:
- the lookup is performed for qualified names which depends on template arguments (which are known at this stage);
- argument-dependent lookup (also known as Andrew Koenig lookup) is performed for unqualified names in order to complete the check initiated on the template definition stage.
Please note, that functions and classes generated from templates are subject to the same rules as for regular functions and classes.
Example of errors during the template instantiation stage:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
namespace library { template<typename T> void print(T value) { // Provokes instantiation stage error if T is // neither std::string nor util::Data static_assert(std::is_same_v<T, std::string> || std::is_same_v<T, util::Data>); // Provokes instantiation stage error if // util::println(T) is not defined and called ... if constexpr (std::is_same_v<T, std::string>) util::println(value); // ... being qualified else println(value); // ... being unqualified } } // namespace library |