How do you access the body of a template function in the AST?

Given this source file:

template <int N>
bool fn()
{
    return N > 1;
}

clang -Xclang -ast-dump -fsyntax-only tmp.cpp reports:

TranslationUnitDecl 0x2a354087370 <<invalid sloc>> <invalid sloc>
|-TypedefDecl 0x2a3540878b0 <<invalid sloc>> <invalid sloc> implicit __int128_t '__int128'
| `-BuiltinType 0x2a3540875c0 '__int128'
|-TypedefDecl 0x2a354087918 <<invalid sloc>> <invalid sloc> implicit __uint128_t 'unsigned __int128'
| `-BuiltinType 0x2a3540875e0 'unsigned __int128'
|-CXXRecordDecl 0x2a354087968 <<invalid sloc>> <invalid sloc> implicit class type_info
| `-TypeVisibilityAttr 0x2a354087a30 <<invalid sloc>> Implicit Default
|-TypedefDecl 0x2a354087a88 <<invalid sloc>> <invalid sloc> implicit size_t 'unsigned long long'
| `-BuiltinType 0x2a354087540 'unsigned long long'
|-TypedefDecl 0x2a354087b18 <<invalid sloc>> <invalid sloc> implicit __builtin_ms_va_list 'char *'
| `-PointerType 0x2a354087ae0 'char *'
|   `-BuiltinType 0x2a354087400 'char'
|-TypedefDecl 0x2a354087b78 <<invalid sloc>> <invalid sloc> implicit __builtin_va_list 'char *'
| `-PointerType 0x2a354087ae0 'char *'
|   `-BuiltinType 0x2a354087400 'char'
`-FunctionTemplateDecl 0x2a354087d48 <c:\tmp\tmp.cpp:1:1, line:2:9> col:6 fn
  |-NonTypeTemplateParmDecl 0x2a354087be0 <line:1:11, col:15> col:15 'int' N
  `-FunctionDecl 0x2a354087ca0 <line:2:1, col:9> col:6 fn '_Bool (void)'
    `-<<<NULL>>>

Why is the body NULL? Where is it represented in the AST?

That looks like delayed template instantiation behavior (the default you get on Windows, because that’s how MSVC behaves). You have to instantiate the template in order to generate its body in that case (or disable delayed template parsing with -fno-delayed-template-parsing).

OK, yes, that does it. Thanks

Doesn’t this imply that clang-tidy checks are always missing bits when run on Windows?

Not usually. A lot of the tests specify -fno-delayed-template-parsing or instantiate the templates somehow. However, we do run into the issue where someone forgets to do that and build bots break with some degree of regularity. (The same happens in Clang when writing SemaCXX template tests.)

I think I’ll put a mention of this in the clang-tidy contributing docs.

1 Like

IMO it’s high time to reconsider the default setting for this flag. This MSVC compatibility is regularly causing more issues than it solves. It most recently came up in this code review in 2021, which as I understand it did not land:
https://reviews.llvm.org/D103772

Previously it was mentioned in an issue in 2019:

https://github.com/llvm/llvm-project/issues/42377

As I understand it, flipping the default here would be inconsistent with MSVC. Some people look at this issue and say, “MSVC uses /Zc:twoPhase- by default, so clang-cl should follow MSVC”. However, Clang’s -fdelayed-template-parsing setting isn’t really the same thing as /Zc:twoPhase-, and I think if we can address the build failure from last time, users will get more value from a more C+±conforming default compiler behavior. So, I think we should go ahead and make the change anyway.

I think an RFC to reconsider that behavior would be interesting, because I agree, it’s a disruptive behavior to have on by default. However, I think we need be very cautious about breaking system headers. I’m not super worried about most Win32 headers given that they’re generally C headers. But I’m definitely worried about the massive amount of e.g., MFC (and other more C++ heavy) code that still exists.

(A secondary concern would be silent ABI breakage, but I’m not certain changing this default should impact ABI at all – if the template was being instantiated, ABI won’t be changed, and if the template was never instantiated, it was never ODR-used to begin with and so there should be no ABI impact either.)

Is this a Windows-only problem though? We use Linux and experience many situations where clang-tidy will not catch issues simply due to templates not being instantiated. From this thread my understanding is that “delayed template instantiation” is a feature enabled by default only on Windows; why does it happen on Linux too? For example:

template <typename T>
T foo(int x)
{
    return T(x);  // Expect: google-readability-casting
}

int main()
{
    float x = foo<float>(123);  // Only when the function is instantiated we get the warning
}

However, I think we need be very cautious about breaking system headers. … But I’m definitely worried about the massive amount of e.g., MFC (and other more C++ heavy) code that still exists.

I’m optimistic that MFC, ATL, and other C++ template libraries already work without delayed template parsing because Microsoft added the /permissive- flag to new VS projects by default a long time ago. That means that most templates written by Microsoft should parse correctly where they are written, and not later at the point of instantiation.

There’s always some user somewhere that relies on every compiler implementation detail, but I feel pretty confident that we can find a way to make this flag flip. It’s certainly release noteworthy.

Re: ABI, yep, this should be pretty safe.

Is this a Windows-only problem though? We use Linux and experience many situations where clang-tidy will not catch issues simply due to templates not being instantiated.

I think what you describe is the expected behavior.

1 Like

There are also a significant amount of warnings that are only possible once expression is instantiated. I don’t know much about Tidy, but in the CFE itself we have a bunch that are like that.

For example:

return T(x);

might just be a constructor call, at which point it makes little sense to diagnose it as a ‘readability cast’, IMO.

I share that optimism, but I’d want to know how we fare against the oldest headers we expect to support rather than the newest. But if the newest work well but previous versions are a problem, this at least sets a timer for when we can enable the functionality by default (or it may inform us that we can never switch the defaults, but that would be a surprise to me).

+1; if the breakage doesn’t happen in a system or “commonly used 3rd party header” (whatever that means to folks), I think it’s worth trying. And I agree, warning folks we’re thinking about doing this switch is something we definitely should do. Perhaps we could run an RFC by community and at least get the release note into Clang 15 so that we can flip defaults (if possible) in Clang 16?

Some checks elect to not diagnose at all unless the template is instantiated; you found one of them: llvm-project/AvoidCStyleCastsCheck.cpp at 5bbe50148f3b515c170be22209395b72890f5b8c · llvm/llvm-project · GitHub

1 Like