[cfe-dev] Warn on template def after explicit instantiation?

John McCall rjmccall at apple.com
Mon Jan 7 19:13:41 PST 2013


On Jan 7, 2013, at 9:07 AM, "Robinson, Paul" <Paul.Robinson at am.sony.com> wrote:
> From: John McCall [rjmccall at apple.com]
>> On Jan 4, 2013, at 5:58 PM, "Robinson, Paul" <Paul.Robinson at am.sony.com> wrote:
>>>> On Jan 4, 2013, at 4:44 PM, "Robinson, Paul" <Paul.Robinson at am.sony.com> wrote:
>>>>> 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.



More information about the cfe-dev mailing list