Home > Software design >  Compile-time error in uninstanciated function template
Compile-time error in uninstanciated function template

Time:04-13

My understanding of function templates has always been: if they contain invalid C and you don't instanciate them, your project will compile fine.

However, the following code:

#include <cstdio>
#include <utility>

template <typename T>
void contains_compile_time_error(T&& t) {
    int j = nullptr;
}

int main() {}

Compiles with:

  • x86-64 gcc 11.2 and flag -std=c 20;
  • x64 msvc v19.31 and flag /std:c 20;

and does not with x86-64 clang 14.0.0 (with flags -std=c {11,14,17,20}):

error: cannot initialize a variable of type 'int' with an rvalue of type 'std::nullptr_t'
    int j = nullptr;
        ^   ~~~~~~~
1 error generated.

Even more confusing to me, the following code:

#include <cstdio>
#include <utility>

namespace ns {
struct S {};

template <typename T>
void apply(T&& t) {
    //print(std::forward<T>(t));
    ns::print(std::forward<T>(t));
}

void print(S const&) { std::puts("S"); }

}  // namespace ns

int main() {}

Does not compile with:

  • x86-64 gcc 11.2 and flag -std=c 20;
  • x64 msvc v19.31 and flag /std:c 20;
  • x86-64 clang 14.0.0 and flags -std=c {11,14,17,20};

(all of them complaining that print is not a member of 'ns') but does compile with x64 msvc v19.31 /std:c 17.

If I call unqualified print, then the code compiles with all the above compilers.

So, my questions are:

  • is my understanding of function templates wrong?
  • why do the above compilers behave differently with the code snippets I posted?

Edit 0: as per Frank's comment, x64 msvc v19.31 /std:c 17 /permissive- fails to compile function template apply where I call the qualified ns::print.

CodePudding user response:

My understanding of function templates has always been: if they contain invalid C and you don't instanciate them, your project will compile fine.

Nope, if the code in the template is invalid, then so is the program.

But what does "invalid C " mean? You can have syntactically valid C that is still invalid semantically, and the semantics of C are highly contextual.

So there's multiple levels of "valid" that can be checked at different times during compilation, based on the available information. Critically, there are things that can theoretically be checked early, but are potentially unreasonably difficult. Because of this, compilers are allowed some leeway when it comes to validating the semantics of template definitions. However, whether the code is broken or is not ambiguous, it's a compiler's ability to detect broken code that is.

why do the above compilers behave differently with the code snippets I posted?

As far as int j = nullptr is concerned:

The program is technically ill-formed, but the diagnostics is optional. So The code is broken, but GCC is not breaking compliance by letting it through.

For example, if S only had private constructors, then print(std::forward<T>(t)) would be flaggable as being broken since there is no possibly T that would make it valid. However, requiring compilers to be smart enough to determine this in all cases would be kind of mean.

For print():

Lookups are a bit of a different matter, there are hard rules that are supposed to be followed.

There are three types of lookups involved here.

  • Qualified lookups, such as ns::print
  • Unqualified lookups not involving template parameters, which you'd get if you tried print(S{});
  • Unqualified lookups involving template parameters, such as print(std::forward<T>(t));

Lookups are deferred for the third category, but must still be performed the same as non-template functions for the other two.

Note that the code still needs to be syntactically valid, even in the case of deferred lookups, hence why typename needs to be added when doing a dependent lookup of a type.

And as for MSVC allowing ns::print: That compiler is not compliant by default on this (and other) aspect of the standard. You need to use the /permissive- compiler flag to enforce compliance.

  • Related