Warn on template def after explicit instantiation?

A client ran into the following situation. Suppose we have this:

  // Declarations from a header file.
  template <class T> SomeTemplate {
  public:
    void Init();
  };
  class FooBar { };
  // End of the header file.

  // Here's the initial source module.
  // Explicitly instantiate SomeTemplate.
  template SomeTemplate<FooBar>;
  // Oops, forgot to define Init().
  template <class T> SomeTemplate<T>::Init() { }
  // Now we use the stuff.
  SomeTemplate<FooBar> Obj;
  void foo() {
    Obj.Init();
  }

This built cleanly. But, moving Obj and foo() to another source file
caused a link-time error, because of an undefined reference to
SomeTemplate<FooBar>::Init(). (Call this "version 2.")

The client understands that the original module worked because
"Obj.Init()" implicitly instantiated SomeTemplate<FooBar>::Init() as a
definition, because the template definition existed in the same
compilation unit. And moving the same code to a separate module
implicitly instantiated Init() as a declaration, because that's all
that was in the header file. The "template SomeTemplate<FooBar>;"
didn't instantiate a definition of Init() because that definition hadn't
happened yet; hence, the link-time error. The fix was to move the
explicit instantiation down past the template method definitions.
(Call that one "version 3.")

His question is: Can we have a warning for the situation where a
template definition of a method follows an explicit instantiation of
its containing class? (Or maybe, if a template definition follows an
explicit instantiation of its declaration?) He argues that while it's
well-defined by the standard, it's probably a mistake on the
programmer's part (as it was for his code), and a compile-time
diagnostic would have made it a lot easier to figure out.

For completeness, I'll mention that MSVC builds version 2 cleanly,
but Clang with -fdelayed-template-parsing does not.

At this point I'm mainly soliciting opinions about the worthiness of
such a warning. It's not clear at the moment whether I could attempt
this myself anytime soon.

Thanks,
--paulr

I'd be skeptical of such a warning. It's certainly reasonable for a
user who's decided to play careful explicit-instantiation games to
be intentionally defining a member after the explicit instantiation
to avoid the instantiation of a particular member. That makes it
hard to justify this warning being default-on — and I can't
really imagine it doing much good being default-off.

John.

One major difference is that that warning can easily be suppressed
through innocuous code changes — changes that, in fact, make the
code more legible to people who haven't memorized the more
obscure parts of the precedence tables. There is no way I can see
to suppress your proposed warning without significantly changing
the behavior of the code — for example, switching an explicit
instantiation of a class to an explicit instantiation of every defined
member of it.

Another major difference is that that warning points out something
that otherwise requires dynamic testing to detect. Your warning
points out something that, if it matters at all, is going to cause the
build to fail anyway. We still do like to warn about those things when
we can, but it does make it a lot less important.

A third difference is that I consider (and I think most people would
agree with me) explicit instantiation of out-of-line definitions of class
template members to be an expert technique. That makes it more
likely that the user really is intending to do something subtle.

All that said, it would be reasonable to have a warning about explicit
instantiations that don't seem to be necessary. If I had to make a
none-too-careful first guess at the criteria for such a warning, it would
be explicit instantiations of class templates that (1) aren't preceded
by an explicit instantiation declaration and (2) don't instantiate any
methods that aren't defined in the class definition. If that would catch
your user's case, then great.

John.

A client ran into the following situation. Suppose we have this:

// Declarations from a header file.
template <class T> SomeTemplate {
public:
  void Init();
};
class FooBar { };
// End of the header file.

// Here's the initial source module.
// Explicitly instantiate SomeTemplate.
template SomeTemplate<FooBar>;
// Oops, forgot to define Init().
template <class T> SomeTemplate<T>::Init() { }
// Now we use the stuff.
SomeTemplate<FooBar> Obj;
void foo() {
  Obj.Init();
}

This built cleanly. But, moving Obj and foo() to another source file
caused a link-time error, because of an undefined reference to
SomeTemplate<FooBar>::Init(). (Call this "version 2.")

The client understands that the original module worked because
"Obj.Init()" implicitly instantiated SomeTemplate<FooBar>::Init() as a
definition, because the template definition existed in the same
compilation unit. And moving the same code to a separate module
implicitly instantiated Init() as a declaration, because that's all
that was in the header file. The "template SomeTemplate<FooBar>;"
didn't instantiate a definition of Init() because that definition hadn't
happened yet; hence, the link-time error. The fix was to move the
explicit instantiation down past the template method definitions.
(Call that one "version 3.")

His question is: Can we have a warning for the situation where a
template definition of a method follows an explicit instantiation of
its containing class? (Or maybe, if a template definition follows an
explicit instantiation of its declaration?) He argues that while it's
well-defined by the standard, it's probably a mistake on the
programmer's part (as it was for his code), and a compile-time
diagnostic would have made it a lot easier to figure out.

I'd be skeptical of such a warning. It's certainly reasonable for a
user who's decided to play careful explicit-instantiation games to
be intentionally defining a member after the explicit instantiation
to avoid the instantiation of a particular member. That makes it
hard to justify this warning being default-on — and I can't
really imagine it doing much good being default-off.

It's true that a template-savvy coder could devise something where some
methods did not exist for certain specializations. But...
I should think the more common use-case is somebody porting code from
MSVC or other less-careful compiler to Clang, and running into this
unintentionally.

As a parallel example, Clang now complains if I fail to parenthesize
expressions with multiple operators where it thinks I might not be
getting the precedence right. This is not exactly expert-friendly
either. I think this is another example in the same vein.

One major difference is that that warning can easily be suppressed
through innocuous code changes — changes that, in fact, make the
code more legible to people who haven't memorized the more
obscure parts of the precedence tables. There is no way I can see
to suppress your proposed warning without significantly changing
the behavior of the code — for example, switching an explicit
instantiation of a class to an explicit instantiation of every defined
member of it.

Another major difference is that that warning points out something
that otherwise requires dynamic testing to detect. Your warning
points out something that, if it matters at all, is going to cause the
build to fail anyway. We still do like to warn about those things when
we can, but it does make it a lot less important.

Okay, I can see that.

A third difference is that I consider (and I think most people would
agree with me) explicit instantiation of out-of-line definitions of class
template members to be an expert technique. That makes it more
likely that the user really is intending to do something subtle.

But not if you're porting code from a Microsoft environment.
Would a warning in Microsoft compatibility mode be reasonable?

All that said, it would be reasonable to have a warning about explicit
instantiations that don't seem to be necessary. If I had to make a
none-too-careful first guess at the criteria for such a warning, it would
be explicit instantiations of class templates that (1) aren't preceded
by an explicit instantiation declaration and (2) don't instantiate any
methods that aren't defined in the class definition. If that would catch
your user's case, then great.

I can't say I completely followed that, but it doesn't sound like it
matches my user's case.

From: John McCall [rjmccall@apple.com]

A client ran into the following situation. Suppose we have this:

// Declarations from a header file.
template <class T> SomeTemplate {
public:
void Init();
};
class FooBar { };
// End of the header file.

// Here's the initial source module.
// Explicitly instantiate SomeTemplate.
template SomeTemplate<FooBar>;
// Oops, forgot to define Init().
template <class T> SomeTemplate<T>::Init() { }
// Now we use the stuff.
SomeTemplate<FooBar> Obj;
void foo() {
Obj.Init();
}

This built cleanly. But, moving Obj and foo() to another source file
caused a link-time error, because of an undefined reference to
SomeTemplate<FooBar>::Init(). (Call this "version 2.")

The client understands that the original module worked because
"Obj.Init()" implicitly instantiated SomeTemplate<FooBar>::Init() as a
definition, because the template definition existed in the same
compilation unit. And moving the same code to a separate module
implicitly instantiated Init() as a declaration, because that's all
that was in the header file. The "template SomeTemplate<FooBar>;"
didn't instantiate a definition of Init() because that definition hadn't
happened yet; hence, the link-time error. The fix was to move the
explicit instantiation down past the template method definitions.
(Call that one "version 3.")

His question is: Can we have a warning for the situation where a
template definition of a method follows an explicit instantiation of
its containing class? (Or maybe, if a template definition follows an
explicit instantiation of its declaration?) He argues that while it's
well-defined by the standard, it's probably a mistake on the
programmer's part (as it was for his code), and a compile-time
diagnostic would have made it a lot easier to figure out.

I'd be skeptical of such a warning. It's certainly reasonable for a
user who's decided to play careful explicit-instantiation games to
be intentionally defining a member after the explicit instantiation
to avoid the instantiation of a particular member. That makes it
hard to justify this warning being default-on — and I can't
really imagine it doing much good being default-off.

It's true that a template-savvy coder could devise something where some
methods did not exist for certain specializations. But...
I should think the more common use-case is somebody porting code from
MSVC or other less-careful compiler to Clang, and running into this
unintentionally.

As a parallel example, Clang now complains if I fail to parenthesize
expressions with multiple operators where it thinks I might not be
getting the precedence right. This is not exactly expert-friendly
either. I think this is another example in the same vein.

One major difference is that that warning can easily be suppressed
through innocuous code changes — changes that, in fact, make the
code more legible to people who haven't memorized the more
obscure parts of the precedence tables. There is no way I can see
to suppress your proposed warning without significantly changing
the behavior of the code — for example, switching an explicit
instantiation of a class to an explicit instantiation of every defined
member of it.

Another major difference is that that warning points out something
that otherwise requires dynamic testing to detect. Your warning
points out something that, if it matters at all, is going to cause the
build to fail anyway. We still do like to warn about those things when
we can, but it does make it a lot less important.

Okay, I can see that.

A third difference is that I consider (and I think most people would
agree with me) explicit instantiation of out-of-line definitions of class
template members to be an expert technique. That makes it more
likely that the user really is intending to do something subtle.

But not if you're porting code from a Microsoft environment.

True.

Would a warning in Microsoft compatibility mode be reasonable?

Well, in true Microsoft compatibility mode, we should be emulating
Microsoft's explicit instantiation behavior. I don't think there's a lesser
mode that really means "I'm porting from MSVC".

We could add a warning group for that purpose, and then this warning
could go in that. That would be fine, except that it might be better
overall to have a dedicated rewriting tool for that task. I can't really
answer that.

All that said, it would be reasonable to have a warning about explicit
instantiations that don't seem to be necessary. If I had to make a
none-too-careful first guess at the criteria for such a warning, it would
be explicit instantiations of class templates that (1) aren't preceded
by an explicit instantiation declaration and (2) don't instantiate any
methods that aren't defined in the class definition. If that would catch
your user's case, then great.

I can't say I completely followed that, but it doesn't sound like it
matches my user's case.

You have to be able to see a class's definition in order to use any
of its members. The ODR promises us that everybody seeing a
class definition will see it define the same set of members with the
same definitions. Thus, an explicit instantiation of a class that doesn't
have any out-of-line member definitions (yet) will produce no
function definitions that a normal user of the class couldn't already
produce on demand. (With one exception; see below.)

So given this:
  template <class T> struct A {
    void foo();
    void bar() {}
  };
  template struct A<char>; // we can warn immediately

We can warn here because anybody seeing this template definition
can produce an equivalent definition for A<char>::bar(), so the
explicit instantiation is pointless. But if, between the definition and
the explicit instantiation, you had this definition:
  template <class T> void A<T>::foo() {}
then we couldn't warn, because there might be translation units
that don't see this function definition, so the explicit instantiation
might be necessary.

The exception is that "extern template" (which is now standard in C++11)
can tell other translation units that this explicit instantiation is here.
And this is theoretically a huge problem for this warning, because
the translation unit with the explicit instantiation definition may not
also have the "extern template" declaration. But, to me, requiring
that in order to suppress the warning doesn't seem like too much
of an encumbrance.

John.