With the increased use of constexpr
across the standard library in recent C++ standards, I occasionally run into code that starts to fail when building with Clang (esp. when building with -std=c++20
or -std=c++2b
against recent trunk libc++ or libstdc++) but not with GCC.
I think a faithful stripped-down reproducer is a test.cc
struct S;
template<typename T> constexpr void f1() { T::f(); }
void f2() { f1<S>(); }
struct S { static void f(); };
for which
$ g++ -c test.cc
succeeds while
$ clang++ -c test.cc
test.cc:2:44: error: incomplete type 'S' named in nested name specifier
template<typename T> constexpr void f1() { T::f(); }
^~~
test.cc:3:13: note: in instantiation of function template specialization 'f1<S>' requested here
void f2() { f1<S>(); }
^
test.cc:1:8: note: forward declaration of 'S'
struct S;
^
1 error generated.
fails.
The reasoning given in the comments in Sema::MarkFunctionReferenced
(clang/lib/Sema/SemaExpr.cpp
) looks compelling to me, making it look like Clang would be correct in failing and GCC would be wrong in succeeding? But I’m not actually sure I understand things right…
Also, if the constexpr
is removed, both Clang and GCC succeed (i.e., they apparently only implicitly instantiate f1<S>
at the end of the translation unit, after the definition of S
). Is the translation unit indeed well-formed then, or is this a case of “ill-formed, no diagnostic required” where both Clang and GCC happen to pick a point of instantiation that comes after S
has been defined, not before?