2

I have small example code:

file foo.h:

#pragma once

template <typename T> class FooNoDef {
public:
  void foo(const T& value); // declared and not defined
};

class FooUser {
public:
  template <typename T> static void useFoo(const T& value) {
    FooNoDef<T>{}.foo(value);
  }
};

file xy.h:

#pragma once

struct X {};
struct Y {};

file xy.cpp:

#include "foo.h"
#include "xy.h"
#include <iostream>

template <> void FooNoDef<X>::foo(const X& value) {
  std::cout << "x" << std::endl;
}

template <> void FooNoDef<Y>::foo(const Y& value) {
  std::cout << "y" << std::endl;
}

and finally main.cpp:

#include "foo.h"
#include "xy.h"

int main() {
  FooUser::useFoo(X{});
  FooUser::useFoo(Y{});
  return 0;
}

This code compiles with gcc 11 and clang 13. I suspect my code is ill-formed, but I can't find a definite answer from reading the standard:

Section 13.9.4 [temp.expl.spec] (emphasis mine):

If a template, a member template or a member of a class template is explicitly specialized, a declaration of that specialization shall be reachable from every 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. If the program does not provide a definition for an explicit specialization and either the specialization is used in a way that would cause an implicit instantiation to take place or the member is a virtual member function, the program is ill-formed, no diagnostic required. An implicit instantiation is never generated for an explicit specialization that is declared but not defined.

Section 13.9.2 [temp.inst] (emphasis mine):

[Example 5:

template<class T> struct Z {
  void f();
  void g();
};

void h() {
  Z<int> a;         // instantiation of class Z<int> required
  Z<char>* p;       // instantiation of class Z<char> not required
  Z<double>* q;     // instantiation of class Z<double> not required

  a.f();            // instantiation of Z<int>​::​f() required
  p->g();           // instantiation of class Z<char> required, and
                    // instantiation of Z<char>​::​g() required
}

Nothing in this example requires class Z, Z​::​g(), or Z​::​f() to be implicitly instantiated.** — end example]

I think FooUser::useFoo() does not cause implicit instantiation of FooNoDef::foo() as the example from the standard discussed, but still I never provided a declaration for my explicit specialization of FooNoDef<X> and FooNoDef<Y>. Which rule of C++, if any, do I violate with my example? Would I have to provide explicit specialization declaration template <> void FooNoDef<X>; and template <> void FooNoDef<Y>; strictly before the body of FooUser::useFoo()?

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
pjk
  • 23
  • 3
  • (Almost?) duplicate: https://stackoverflow.com/questions/71335712/template-specialization-in-cpp-file-primary-template-declaration-in-h-file – user17732522 Mar 24 '22 at 10:14

1 Answers1

4

As far as I can see, you are right, though you've put emphasise on the wrong line of the standard:

[...] a declaration of that specialization shall be reachable from every use of that specialization [...]

Within main, both of FooUser::useFoo<X> and FooUser::useFoo<Y> need to be instantiated. These then need to instantiate FooNoDef<X>::foo and FooNoDef<Y>::foo – and here an implicit instantiation would be caused, if no explicit instantiation was available.

However, there only exists a definition within xy.cpp, and that's not visible to main.cpp, and there's no visible declaration – violating above cited phrase, thus your programme indeed is ill-formed.

To fix, you'd need to add a declaration, e.g. in xy.h (note: the header that is included by main.cpp):

template <> void FooNoDef<X>::foo(X const& value);
template <> void FooNoDef<Y>::foo(Y const& value);
Aconcagua
  • 24,880
  • 4
  • 34
  • 59
  • Thank you for the answer! Would anything change in the reasoning you provided if I removed `static` from `FooUser::useFoo()` and changed main: `FooUser fooUser{}; fooUser.useFoo(X{}); fooUser.useFoo(Y{});`? – pjk Mar 24 '22 at 10:34
  • @pjk The function is called, so it needs to be instantiated – and together with the function the used template needs to be instantiated – now way around and totally independent from being static or not. Could be a free standing function as well, still wouldn't change. For all these cases, the declaration needs to be available as specified by the standard. It isn't, so ill-formed programme. – Aconcagua Mar 24 '22 at 13:28