Page MenuHomePhabricator

Fix core-DR1755 & 727 & llvm-PR17294 & PR16906 - partial & explicit specializations of member templates (class/variable/function)
Needs ReviewPublic

Authored by faisalv on Apr 21 2014, 8:39 PM.

Details

Summary

This patch attempts to address issues related to member template
specializations.

As a quick review (incorporating direction from Issaquah):

  1. Full specializations of function, class or variable member templates can be declared at class or namespace scope.
  1. Partial specializations of a class or variable member template can be declared similarly.
  2. A full or partial specialization, or a primary member template can be explictly declared for an implicitly instantiated class specialization.
    • if the primary template is so explicitly declared it does not inherit partial specializations from the unspecialized prototype template.
    • if a full or partial specialization is so explicitly declared it does but wins out over other specializations that contains fewer empty template parameter lists.
  1. Explicitly declared specializations (full or partial) of member templates are only substituted into:
    • a class specialization needs to have its type completed
    • a variable specialization is referenced (to determine type), and then when its value (instantiate initializer) is needed.
    • once a function template is selected, and its declaration is being instantiated.
  2. During substitution of any of the explicitly declared partial or full specializations if an error occurs, that declaration is skipped and is not considered either in the partial ordering, or explicit specialization search.

Following are some examples:

 
template<class T, class> struct A {
   template<class U> static A<U, T> var;
   template<class U> static U var<U*>;
   template<> static char var<char>;    
   
   template<class U> struct B {
     template<class V1, class V2> V1* foo(V2);
     template<> typename T::type* foo<typename T::type>(U) { return 0; }
   };
   
   template<class U> struct B<U*>;
};

template<class T, class T2> template<>
struct A<T, T2>::B<typename T::type> { };

template<class T, class T2> template<>
struct A<T, T2>::B<float> { };

template<class T, class T2> template<class U> template<>
char* A<T, T2>::B<U>::foo(T2) { return 0; }

template<> template<class U>
auto A<int>::var<U*> = [](U*) { };

template<> template<class U>
auto A<char>::var = [](U*, char) { };

Additionally, explicitly declared specializations (partial or full) of constexpr
member templates always require an initializer when in-class:

  • if they are then declared out of class, it is treated as a definition and can not have an initializer
  • For variable templates (and their partial or full specializations) first declared out of line as constexpr they must have an initializer - and subsequently represent a definition.

For class templates, if a partial or full specialization is declared
after a potential use, it is an error.

For variables templates:

  • if a partial or full specialization is declared after the 'type' of a variable specialization (that would have used the expicitly declared specialization) has been used, it is only valid if we can prove that the type will be the same.
  • When the variable specialization's initializer is needed (that is its value is required), we once again search for the best specialization declaration, and if all is consistent (type matches, value has not been used etc.), we use its value.

Patch Implementation Notes:

I tried to comment the patch with code examples - but the comments do need
improvement. The patch is also short on good diagnostics.

I also introduced checking of class scope function specializations within
a dependent context by generating/inventing template arguments for each
enclosing template parameter list - so that we can compare function
templates, order them (requires their template depth to be zero)
and then link the full-specialization to its appropriate primary
template.

The alternatives were:

  1. to postpone all analysis in the dependent context (status quo) and only when the outer template arguments have been substituted, do the analysis of the full specializations (the problem with this is you can write some real non-sense in a dependent context that could never specialize any of your member function templates) and they would always potentially just get SFINAE'd out.
  2. consider teaching TemplateDeclInstantiator & TemplateInstantiator to do substitution of template parameters at specific template depths or focus only on certain depths when doing deduction (currently when we do partial ordering, the template delcaration's template parameters must have a depth of 0).

Not sure if I made the right choice, but it seems to work reasonably
well.

I also merged some of the logic for handling partial specializations
of class and variable templates.

Looking forward to some feedback.

Thanks!

Diff Detail

Event Timeline

faisalv updated this revision to Diff 9236.May 8 2014, 8:41 PM
faisalv edited edge metadata.

Have partial specializations of variable templates behave similarly to the class partial specializations.

  • merges some of the logic of dealing with partial specializations
  • member variable templates can be explicitly specialized as primary templates or partial specializations as long as the type does not change or the value has not been used.

Will appreciate any feedback!
thanks

rsmith edited edge metadata.May 22 2014, 4:45 PM

Sorry for the delay, this hit my spam filter. This patch will need more reviewing than I have time for today, I'll try to come back to it tomorrow or next week. Please ping vigorously if you don't hear from me next week!

faisalv updated this revision to Diff 9775.May 23 2014, 1:41 PM
faisalv retitled this revision from Fix core-DR1755 & llvm-PR17294 & PR16906 - partial specializations of class member templates to Fix core-DR1755 & 727 & llvm-PR17294 & PR16906 - partial & explicit specializations of member templates (class/variable/function).
faisalv updated this object.
faisalv edited edge metadata.

Added support for class scope explicit specializations of member template functions, variables and classes.

Will add a more detailed overview of the changes.

Essentially the following code now works (and similarly for class and variable templates):

template<class T> struct A {

template<class V> V* foo(V*, T*) { return 0; }

};

template<class T> template<>
char* A<T>::foo(char*, T*) { return (char*)(T*)0; }

char *pc = A<int>{}.foo((char*)0, (int*)0);

Please look at the testfile sematemplate/member-templates.cpp for more in depth examples.

Will modify the code once I have feedback!

no worries Richard - thanks for agreeing to put some time aside for this.

The patch that i just uploaded should implement DR727 too (that is in
class explicit specializations within class templates).

My sense is that it should probably be broken up into smaller patches
...your suggestions here will also be appreciated.

I shall try and write up a paper for core that just summarizes what
was implemented (that might be useful when those issues are discussed
in core (no promises ;))

http://reviews.llvm.org/D3445

Thanks!
Faisal Vali

faisalv updated this revision to Diff 9778.May 23 2014, 1:54 PM

Remove some debugging related changes I did not intend to submit.

Sorry about the noise.

faisalv updated this revision to Diff 9821.May 26 2014, 9:59 PM
faisalv updated this object.

This patch attempts to address issues related to member template
specializations.

As a quick review (incorporating direction from Issaquah):

  1. Full specializations of function, class or variable member templates can be declared at class or namespace scope.
  1. Partial specializations of a class or variable member template can be declared similarly.
  2. A full or partial specialization, or a primary member template can be explictly declared for an implicitly instantiated class specialization.
    • if the primary template is so explicitly declared it does not inherit partial specializations from the unspecialized prototype template.
    • if a full or partial specialization is so explicitly declared it does but wins out over other specializations that contains fewer empty template parameter lists.
  1. Explicitly declared specializations (full or partial) of member templates are only substituted into:
    • a class specialization needs to have its type completed
    • a variable specialization is referenced (to determine type), and then when its value (instantiate initializer) is needed.
    • once a function template is selected, and its declaration is being instantiated.
  2. During substitution of any of the explicitly declared partial or full specializations if an error occurs, that declaration is skipped and is not considered either in the partial ordering, or explicit specialization search.

Following are some examples:

 
template<class T, class> struct A {
   template<class U> static A<U, T> var;
   template<class U> static U var<U*>;
   template<> static char var<char>;    
   
   template<class U> struct B {
     template<class V1, class V2> V1* foo(V2);
     template<> typename T::type* foo<typename T::type>(U) { return 0; }
   };
   
   template<class U> struct B<U*>;
};

template<class T, class T2> template<>
struct A<T, T2>::B<typename T::type> { };

template<class T, class T2> template<>
struct A<T, T2>::B<float> { };

template<class T, class T2> template<class U> template<>
char* A<T, T2>::B<U>::foo(T2) { return 0; }

template<> template<class U>
auto A<int>::var<U*> = [](U*) { };

template<> template<class U>
auto A<char>::var = [](U*, char) { };

Additionally, explicitly declared specializations (partial or full) of constexpr
member templates always require an initializer when in-class:

  • if they are then declared out of class, it is treated as a definition and can not have an initializer
  • For variable templates (and their partial or full specializations) first declared out of line as constexpr they must have an initializer - and subsequently represent a definition.

For class templates, if a partial or full specialization is declared
after a potential use, it is an error.

For variables templates:

  • if a partial or full specialization is declared after the 'type' of a variable specialization (that would have used the expicitly declared specialization) has been used, it is only valid if we can prove that the type will be the same.
  • When the variable specialization's initializer is needed (that is its value is required), we once again search for the best specialization declaration, and if all is consistent (type matches, value has not been used etc.), we use its value.

Patch Implementation Notes:

I tried to comment the patch with code examples - but the comments do need
improvement. The patch is also short on good diagnostics.

I also introduced checking of class scope function specializations within
a dependent context by generating/inventing template arguments for each
enclosing template parameter list - so that we can compare function
templates, order them (requires their template depth to be zero)
and then link the full-specialization to its appropriate primary
template.

The alternatives were:

  1. to postpone all analysis in the dependent context (status quo) and only when the outer template arguments have been substituted, do the analysis of the full specializations (the problem with this is you can write some real non-sense in a dependent context that could never specialize any of your member function templates) and they would always potentially just get SFINAE'd out.
  2. consider teaching TemplateDeclInstantiator & TemplateInstantiator to do substitution of template parameters at specific template depths or focus only on certain depths when doing deduction (currently when we do partial ordering, the template delcaration's template parameters must have a depth of 0).

Not sure if I made the right choice, but it seems to work reasonably
well.

I also merged some of the logic for handling partial specializations
of class and variable templates.

Looking forward to some feedback.

Thanks!

faisalv updated this revision to Diff 10792.Jun 24 2014, 9:39 AM

*ping* Richard - I know we were thinking about reviewing this in Rapperswil - but couldn't make it work. Any chance I can get you to give me some feedback soon please =)

Also provided is a link to a paper that (might get changed for the pre-rapperswil mailing based on your feedback) summarizes the changes: https://groups.google.com/a/isocpp.org/forum/?fromgroups#!topic/std-discussion/k2yMqp5EnTY

Thanks!

*ping* Richard please! =)

Restarted reviewing this, then noticed you've updated it since I last worked on it. Alas. So... you get two different versions of me reviewing the first ~half of this patch :)

include/clang/AST/DeclTemplate.h
668

Maybe isCXXClassMember()?

FIXME: How do we get here for something that's not instantiated from a member?

807–809

This whitespace change doesn't reflect our common practice, which is to put a blank line before public: and no blank line afterwards.

include/clang/Sema/Sema.h
364–370

I don't think we can explicitly specialize in either case. If we needed to instantiate the initializer for any reason, then it's too late to explicitly specialize.

371–372

Can you use the Used flag for this instead?

FIXME: Is this missing serialization?

5295–5297

Do we really need both TemplateParams and TemplateParameterLists?

6086

Please use /// comments here and below.

6087–6093

Doxygenify this:

  1. Use ///, not // (here and elsewhere).
  2. To apply this doc comment to both following functions, put //@{ on the line before the first function and //@} on the line after the second one.
6088–6090

Seems complex enough that a template<typename Decl> struct MostSpecializedPartialSpecialization would help.

6108–6109

It's generally preferable and conventional to pass in a SmallVectorImpl by reference rather than returning a SmallVector by value, so the caller gets to choose the SmallVector size.

6170–6175

Is this any different from DeducedTemplateArgumentSubstitution?

6174

Wow, that's rather long. Maybe just ExplicitFunctionSpecializationSubstitution?

lib/AST/ASTContext.cpp
7933–7949

This should be covered by isThisDeclarationADefinition, shouldn't it? Maybe the problem is in that function instead? This use of A<char>::B<float> should leave A<char>::B<int> as being only a declaration.

lib/AST/Decl.cpp
1754–1755

I'd suggest dropping one of these. Probably the second one.

1766

Does it really matter whether the surrounding class template specialization is implicitly or explicitly instantiated? I would think you just want to know whether it isTemplateInstantiation.

Also, is this just isMemberSpecialization()?

1767

I think MSVC version <old> requires an explicit return type here? Also, this style is not common in Clang and LLVM; we'd usually just use

bool IsVeryLongNameBlahBlah;
{
  // Compute value and initialize.
}
1767–1781

Please factor this out into a static helper function.

1768

Please use auto when initializing a variable from the result of cast or dyn_cast.

1777–1779

Please add a default reference capture, and use the variables you already have for the first two things here.

1785–1799

That's a really weird rule. Maybe we should take this one back to core. It'd be much simpler to apply the current "definition if it has an initializer" rule in all out-of-line cases.

1805

An early bailout if this is false would be better.

1808–1809

Likewise for this.

1810–1818

I think this should simplify somewhat. Looks like the general rule is: if the variable is templated in any way, then it's a definition if the first declaration was within a class, otherwise it's a definition.

lib/AST/DeclTemplate.cpp
1053–1056

Please use

if (auto *D = P->getInstantiatedFromMember())
  if (D->getCanonicalDecl() == DCanon)

(both here and above).

lib/AST/TemplateBase.cpp
24

Please do not include Sema into AST.

lib/Sema/SemaDecl.cpp
2853

As before, I think the lambda hurts readability here.

2863

This assertion can fail, for instance if the variable templates are at namespace scope.

yay! Thanks for the feedback to the first half - will work on it in
the background - looking forward to the rest of the feedback :)
Faisal Vali