Calling function templates specialized in another translation unit [duplicate]
Asked Answered
I

3

2

I'm working on a codebase which uses the following structure:

a.h:

template<int N> void f();
void b();

a.cpp:

#include "a.h"

template<> void f<1>() {}

int main()
{
    b();
}

b.cpp:

#include "a.h"
void b()
{
    f<1>();
}

The code appears to build and run correctly.

My question is: is this well-formed, or is it some kind of ill-formed NDR that happens to work?


If building with clang -Wundefined-func-template (this was enabled in my IDE's default settings for clang-tidy) then a warning is produced:

b.cpp:5:2: warning: instantiation of function 'f<1>' required here, but no definition is available [-Wundefined-func-template]
        f<1>();
        ^
./a.h:1:22: note: forward declaration of template entity is here
template<int N> void f();
                     ^
b.cpp:5:2: note: add an explicit instantiation declaration to suppress this warning if 'f<1>' is explicitly instantiated in another translation unit
        f<1>();
        ^

But I am not sure whether to just disable the warning, or make some code change (other than moving the explicit specialization definition to the header file, which would not be preferable for this project).

Following the advice in the warning message and adding an explicit instantiation declaration to the header file (i.e. extern template void f<1>();) caused an error message (implicit instantiation of a specialization before explicit instantiation).

However, adding an explicit specialization declaration template<> void f<1>(); to the header file suppresses the warning. But I am not sure if this is (a) necessary, and/or (b) recommended style.

Interpose answered 11/4, 2020 at 1:10 Comment(5)
See [temp]/7 and CWG2138 for confirmation that ill-formed NDR is intended.Busiek
@LanguageLawyer would you be able to write an answer?Interpose
If I'm reading it rightly, the CWG explanation is saying that calls to explicit specializations need a declaration of the specialization in scope at least, but calls to explicit instantiations don'tInterpose
Also Is it safe to place definition of specialization of template member function (withOUT default body) in source file? [link to an answer]Busiek
@LanguageLawyer Good findsInterpose
B
4

[temp]/7 says:

A function template, member function of a class template, variable template, or static data member of a class template shall be defined in every translation unit in which it is implicitly instantiated unless the corresponding specialization is explicitly instantiated in some translation unit; no diagnostic is required.

The standard requires explicit instantiation, so explicit specialization in a.cpp won't make the program well-formed.

A similar question ([temp]/7 treats function templates and member functions of class templates equally) was asked in CWG2138:

It is not clear whether the following common practice is valid by the current rules:

   // foo.h
   template<typename T> struct X {
    int f(); // never defined
   };
   // foo.cc
   #include "foo.h"
   template<> int X<int>::f() { return 123; }
   // main.cc
   #include "foo.h"
   int main() { return X<int>().f(); }

which was closed as NAD with the following rationale:

As stated in the analysis [which referred to [temp]/7, among other things], the intent is for the example to be ill-formed, no diagnostic required.

So, the answer is: the program is ill-formed NDR, and this is intended.

Busiek answered 12/4, 2020 at 2:15 Comment(0)
A
2

There's an example in [temp.over]/5 that matches yours almost exactly, and pronounces it well-formed:

[temp.over]/5 ... [ Example:

template<class T> void f(T); // declaration
void g() {
  f("Annemarie"); // call of f<const char*>
}

The call of f is well-formed even if the template f is only declared and not defined at the point of the call. The program will be ill-formed unless a specialization for f<const char*>, either implicitly or explicitly generated, is present in some translation unit. —end example ]

Aylmar answered 11/4, 2020 at 2:58 Comment(5)
True, but unfortunately the example explanation was just wrong; it was fixed for C++20.Venturesome
@DavisHerring this answer even had a link to the editorial fix in comments, but the author seems to not careBusiek
The example appears to be intact in N4868 "C++20 first post-publication draft (contains editorial fixes to C++20 only)." linked from en.cppreference.com/w/cpp/links. timsong-cpp.github.io/cppwp/n4868/temp.over#5. Can either of you link to the revision you're referencing?Usa
@Usa The example appears to be intact in N4868 Re-read more carefully thenBusiek
Derp, I see it now. "either implicitly or explicitly generated" -> "is explicitly instantiated"Usa
P
1

The program violates [temp.expl.spec]/6:

If a template, a member template or a member of a class template is explicitly specialized then that specialization shall be declared before the first use of that specialization that would cause an implicit instantiation to take place, in every translation unit in which such a use occurs; no diagnostic is required.

The function template f is explicitly specialized by template<> void f<1>() {} in b.cpp. But in the translation unit formed from b.cpp and including a.h, the statement f<1>(); would cause an implicit instantiation of the same specialization f<1>, and there is no declaration of the explicit specialization earlier (or anywhere) in the translation unit.

Per the Standard, an explicit specialization is always a distinct thing from an instantiated specialization, since both can never exist for the same primary template and same template arguments. But the program might work anyway because many compilers use the same mangled linker names for template explicit specializations and instantiated specializations.

The clang warning might be because it's legal, though unusual, to implicitly instantiate a function template without a visible definition if the same specialization is explicitly instantiated, not explicitly specialized, elsewhere. So it's suggesting an improvement to make a legal program clearer. I'm not exactly sure if it actually is legal, though. But its suggested explicit instantiation declaration would be a lie, since the specialization is explicitly specialized, not explicitly instantiated.

The program does become valid if you add explicit specialization declarations to the header file for every specialization which will be used.

template<int N> void f();
template<> void f<1>();
Philpot answered 11/4, 2020 at 2:47 Comment(8)
f<1>() doesn't cause an implicit instantiation to take place. It can't, since no definition for the primary template is provided,.Aylmar
@IgorTandetnik The quote says "would cause". It doesn't precisely name the hypothetical conditions for that "would", but this could be one reason for the phrasing. And [temp.inst]/4 "the function template specialization is implicitly instantiated when the specialization is referenced in a context that requires a definition to exist" isn't stated as conditional on a template definition, so whether or not the absence makes the implicit instantiation ill-formed, I'd say this at least counts for the "would cause an implicit instantiation" condition.Philpot
I don't see how this interpretation can be reconciled with [temp.over]/5 though. It appears that a call to a non-member function doesn't by itself require a definition to exist. Also [temp.inst]/8: "If a function template or a member function template specialization is used in a way that involves overload resolution, a declaration of the specialization is implicitly instantiated." (Emphasis mine)Aylmar
I think I can explain the warning message now: the same message is used whether f<1> happens to be explicitly specialized as in my question, or if a.cpp instead contains a definition of f<int> along with an explicit instantiation of f<1> . I guess they didn't want to go to the effort of having two different warning conditions for those two flavours of the problemInterpose
@IgorTandetnik Both [temp.over]/5 and [temp.inst]/8 are talking about overload resolution. A declaration is instantiated to do overload resolution, but if the template specialization is the best function and the context is an odr-use, then the ODR requires a definition to exist. Another twist: [temp.inst]/1 implies it might sometimes be valid to implicitly instantiate a class template that has not previously been defined.Philpot
@Interpose Well, when compiling b.cpp, the compiler can't look at a.cpp, so can only guess if there will be an explicit specialization or explicit instantiation when later linked into a full program.Philpot
The ODR requires the definition to exist somewhere; it doesn't have to be visible at the call site. The example in [temp.over]/5 shows only one function named f, and states that the call is well-formed as long as there is a matching specialization of f, possibly explicit, in some translation unit, not necessarily the one doing the calling.Aylmar
@IgorTandetnik Probably this should be a chat room or a new question.Philpot

© 2022 - 2024 — McMap. All rights reserved.