Repository: include-what-you-use/include-what-you-use (2026-04-13T17:06:54Z)

Issue:
IWYU suggests removing header needed for explicit template specialization
Consider the following project:
```c++
// test.cpp
#include "a.h"
#include "print.h"

int main() {
    print(42);
    print(true);
    A a;
    // print(a);  // A compiler error by design.
    a.print();
}
```
```c++
// a.h
#pragma once

// IWYU falsely suggests removing this header,
// which makes compilation fail.
#include "print.h"

struct A { void print(); };

template <>
void print(A) = delete;
```
```c++
// a.cpp
#include "a.h"

#include <iostream>

void A::print()
{
    std::cout << "A\n";
}
```
```c++
// print.h
#pragma once

#include <iostream>

template <typename T>
void print(T value) {
    std::cout << "Generic value: " << value << '\n';
}

template <>
inline void print(bool value) {
    std::cout << "Boolean value: " << (value ? "TRUE" : "FALSE") << '\n';
}
```

IWYU output:
```
$ include-what-you-use a.cpp 

a.h should add these lines:

a.h should remove these lines:
- #include "print.h"  // lines 5-5

The full include-list for a.h:
---

(a.cpp has correct #includes/fwd-decls)
```

Following IWYU's suggestion causes compilation to fail, as the compiler requires the primary template declaration from `print.h` for the explicit specialization.


Discussion:
Confirmed.

Out of curiosity: do you really need function template specializations instead of simple overloads?
https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#t144-dont-specialize-function-templates
> Out of curiosity: do you really need function template specializations instead of simple overloads? https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#t144-dont-specialize-function-templates

[Here](https://github.com/bitcoin/bitcoin/blob/5a8a427610ed9672f485ebfefbb8e63b55282636/src/util/fs.h#L227-L232) is the actual code:
```c++
// Disallow path operator<< formatting in tinyformat to avoid locale-dependent
// encoding on windows.
namespace tinyformat {
template<> inline void formatValue(std::ostream&, const char*, const char*, int, const std::filesystem::path&) = delete;
template<> inline void formatValue(std::ostream&, const char*, const char*, int, const fs::path&) = delete;
} // namespace tinyformat
```
Looks like you can just drop `template<>` and still have the same compiler behavior, but without the need to include the primary template declaration.
> Looks like you can just drop `template<>` and still have the same compiler behavior, but without the need to include the primary template declaration.

Of course, I tried that approach :)

Unfortunately, using overloads instead of the template specialization doesn't prevent the disallowed usage as intended. I suspect it’s related to ADL.

UPDATE. For instance, the following diff compiles:
```
--- a/src/addrdb.cpp
+++ b/src/addrdb.cpp
@@ -63,7 +63,7 @@ bool SerializeFileDB(const std::string& prefix, const fs::path& path, const Data
     AutoFile fileout{file};
     if (fileout.IsNull()) {
         remove(pathTmp);
-        LogError("%s: Failed to open file %s\n", __func__, fs::PathToString(pathTmp));
+        LogError("%s: Failed to open file %s\n", __func__, pathTmp);
         return false;
     }
 
```
> I suspect it’s related to ADL.

Right. `formatValue` should be placed into namespace `fs` for `fs::path` overload (but the same would be not correct for `std::filesystem::path` overload), or strictly before the usage in `FormatArg::formatImpl`. So maybe, template specializations are actually justified in your case...



Patch:
diff --git a/iwyu.cc b/iwyu.cc
index ab2eef77e..97384c725 100644
--- a/iwyu.cc
+++ b/iwyu.cc
@@ -5083,6 +5083,16 @@ class IwyuAstConsumer
     return Base::VisitClassTemplateSpecializationDecl(decl);
   }
 
+  bool VisitVarTemplateSpecializationDecl(VarTemplateSpecializationDecl* decl) {
+    if (CanIgnoreCurrentASTNode())
+      return true;
+    if (decl->isExplicitSpecialization())
+      ReportDeclUse(CurrentLoc(), decl->getSpecializedTemplate());
+    // TODO(bolshakov): handle explicit instantiations. Looks like clang doesn't
+    // store all of the explicit instantiation redeclarations in the AST.
+    return Base::VisitVarTemplateSpecializationDecl(decl);
+  }
+
   // Track use of namespace in using directive decl
   // a.h:
   //   namespace a { ... };
@@ -5181,6 +5191,21 @@ class IwyuAstConsumer
         }
       }
     }
+    if (decl->getTemplateSpecializationKind() ==
+        clang::TSK_ExplicitSpecialization) {
+      if (const FunctionTemplateDecl* tpl_decl = decl->getPrimaryTemplate()) {
+        // Report the most recent redeclaration prior to the specialization
+        // (because if there is one in the specialization file, there is no need
+        // to include any other). 'getMostRecentDecl()' returns the last redecl
+        // in the TU which may appear _after_ the specialization.
+        tpl_decl = tpl_decl->getMostRecentDecl();
+        while (IsBeforeInTranslationUnit(decl, tpl_decl))
+          tpl_decl = tpl_decl->getPreviousDecl();
+        ReportDeclUse(CurrentLoc(), tpl_decl);
+      }
+    }
+    // TODO(bolshakov): handle explicit instantiations. Looks like clang doesn't
+    // store all of the explicit instantiation redeclarations in the AST.
     return Base::VisitFunctionDecl(decl);
   }
 


Test patch:
diff --git a/tests/bugs/1931/1931.cc b/tests/bugs/1931/1931.cc
deleted file mode 100644
index a54c79459..000000000
--- a/tests/bugs/1931/1931.cc
+++ /dev/null
@@ -1,25 +0,0 @@
-//===--- 1931.cc - iwyu test ----------------------------------------------===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-
-// IWYU_XFAIL
-
-#include "1931.h"
-
-#include <iostream>
-
-void A::print()
-{
-    std::cout << "A\n";
-}
-
-/**** IWYU_SUMMARY
-
-(tests/bugs/1931/1931.cc has correct #includes/fwd-decls)
-
-***** IWYU_SUMMARY */
diff --git a/tests/bugs/1931/1931.h b/tests/bugs/1931/1931.h
deleted file mode 100644
index 0a93915b1..000000000
--- a/tests/bugs/1931/1931.h
+++ /dev/null
@@ -1,25 +0,0 @@
-//===--- 1931.h - iwyu test -----------------------------------------------===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-
-#pragma once
-
-// IWYU falsely suggests removing this header,
-// which makes compilation fail.
-#include "print.h"
-
-struct A { void print(); };
-
-template <>
-void print(A) = delete;
-
-/**** IWYU_SUMMARY
-
-(tests/bugs/1931/1931.h has correct #includes/fwd-decls)
-
-***** IWYU_SUMMARY */
diff --git a/tests/bugs/1931/print.h b/tests/bugs/1931/print.h
deleted file mode 100644
index 60f951207..000000000
--- a/tests/bugs/1931/print.h
+++ /dev/null
@@ -1,22 +0,0 @@
-//===--- print.h - iwyu test ----------------------------------------------===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-
-#pragma once
-
-#include <iostream>
-
-template <typename T>
-void print(T value) {
-    std::cout << "Generic value: " << value << '\n';
-}
-
-template <>
-inline void print(bool value) {
-    std::cout << "Boolean value: " << (value ? "TRUE" : "FALSE") << '\n';
-}
diff --git a/tests/cxx/specialization_needs_decl-i1.h b/tests/cxx/specialization_needs_decl-i1.h
index 42e87787e..ce5570a21 100644
--- a/tests/cxx/specialization_needs_decl-i1.h
+++ b/tests/cxx/specialization_needs_decl-i1.h
@@ -16,3 +16,24 @@ struct Template;
 
 // Specialization for int
 template<> struct Template<int> { int x; };
+
+template <typename>
+void TplFn1() {
+}
+
+template <typename>
+void TplFn2() {
+}
+
+template <typename>
+void TplFn3() {
+}
+
+template <typename>
+int var_tpl1;
+
+template <typename>
+int var_tpl2;
+
+template <typename>
+int var_tpl3;
diff --git a/tests/cxx/specialization_needs_decl.cc b/tests/cxx/specialization_needs_decl.cc
index fd25e7326..2f2ace21f 100644
--- a/tests/cxx/specialization_needs_decl.cc
+++ b/tests/cxx/specialization_needs_decl.cc
@@ -9,9 +9,9 @@
 
 // IWYU_ARGS: -I .
 
-// Tests that when we specialize a template class, we require a declaration
-// of that class.  However, forward-declaring a specialization doesn't
-// require a definition.
+// Tests that when we specialize a template, we require a primary template
+// declaration to be available. However, forward-declaring a specialization
+// doesn't require a definition.
 
 #include "tests/cxx/specialization_needs_decl-d1.h"
 
@@ -30,6 +30,48 @@ int f(Template<int>& t) {
   return t.x;
 }
 
+// Function template specialization requires a primary template declaration.
+template <>
+// IWYU: TplFn1 is...*specialization_needs_decl-i1.h
+void TplFn1<int>();
+
+template <typename>
+void TplFn2();
+// A primary template redeclaration is already present in this file.
+template <>
+void TplFn2<int>();
+
+// The primary template redeclaration from this file is irrelevant because it is
+// after the specialization.
+template <>
+// IWYU: TplFn3 is...*specialization_needs_decl-i1.h
+void TplFn3<int>();
+template <typename>
+void TplFn3();
+
+// Variable template specializations require a primary template declaration.
+template <>
+// IWYU: var_tpl1 is...*specialization_needs_decl-i1.h
+int var_tpl1<char>;
+
+template <typename T>
+// IWYU: var_tpl1 is...*specialization_needs_decl-i1.h
+int var_tpl1<T*>;
+
+template <typename>
+extern int var_tpl2;
+// A primary template redeclaration is already present in this file.
+template <>
+int var_tpl2<char>;
+
+// The primary template redeclaration from this file is irrelevant because it is
+// after the specialization.
+template <>
+// IWYU: var_tpl3 is...*specialization_needs_decl-i1.h
+int var_tpl3<char>;
+template <typename>
+extern int var_tpl3;
+
 /**** IWYU_SUMMARY
 
 tests/cxx/specialization_needs_decl.cc should add these lines:
@@ -40,7 +82,7 @@ tests/cxx/specialization_needs_decl.cc should remove these lines:
 - #include "tests/cxx/specialization_needs_decl-d1.h"  // lines XX-XX
 
 The full include-list for tests/cxx/specialization_needs_decl.cc:
-#include "tests/cxx/specialization_needs_decl-i1.h"  // for Template
+#include "tests/cxx/specialization_needs_decl-i1.h"  // for Template, TplFn1, TplFn3, var_tpl1, var_tpl3
 template <typename T> struct TplStruct;
 
 ***** IWYU_SUMMARY */