forked from OSchip/llvm-project
[clang] Add the exclude_from_explicit_instantiation attribute
Summary: This attribute allows excluding a member of a class template from being part of an explicit template instantiation of that class template. This also makes sure that code using such a member will not take for granted that an external instantiation exists in another translation unit. The attribute was discussed on cfe-dev at [1] and is primarily motivated by the removal of always_inline in libc++ to control what's part of the ABI (see links in [1]). [1]: http://lists.llvm.org/pipermail/cfe-dev/2018-August/059024.html rdar://problem/43428125 Reviewers: rsmith Subscribers: dexonsmith, cfe-commits Differential Revision: https://reviews.llvm.org/D51789 llvm-svn: 343790
This commit is contained in:
parent
9b1cc0121c
commit
d269579a97
|
@ -3042,6 +3042,13 @@ def InternalLinkage : InheritableAttr {
|
|||
let Documentation = [InternalLinkageDocs];
|
||||
}
|
||||
|
||||
def ExcludeFromExplicitInstantiation : InheritableAttr {
|
||||
let Spellings = [Clang<"exclude_from_explicit_instantiation">];
|
||||
let Subjects = SubjectList<[Var, Function, CXXRecord]>;
|
||||
let Documentation = [ExcludeFromExplicitInstantiationDocs];
|
||||
let MeaningfulToClassTemplateDefinition = 1;
|
||||
}
|
||||
|
||||
def Reinitializes : InheritableAttr {
|
||||
let Spellings = [Clang<"reinitializes", 0>];
|
||||
let Subjects = SubjectList<[NonStaticNonConstCXXMethod], ErrorDiag>;
|
||||
|
|
|
@ -2975,6 +2975,68 @@ This can be used to contain the ABI of a C++ library by excluding unwanted class
|
|||
}];
|
||||
}
|
||||
|
||||
def ExcludeFromExplicitInstantiationDocs : Documentation {
|
||||
let Category = DocCatFunction;
|
||||
let Content = [{
|
||||
The ``exclude_from_explicit_instantiation`` attribute opts-out a member of a
|
||||
class template from being part of explicit template instantiations of that
|
||||
class template. This means that an explicit instantiation will not instantiate
|
||||
members of the class template marked with the attribute, but also that code
|
||||
where an extern template declaration of the enclosing class template is visible
|
||||
will not take for granted that an external instantiation of the class template
|
||||
would provide those members (which would otherwise be a link error, since the
|
||||
explicit instantiation won't provide those members). For example, let's say we
|
||||
don't want the ``data()`` method to be part of libc++'s ABI. To make sure it
|
||||
is not exported from the dylib, we give it hidden visibility:
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
// in <string>
|
||||
template <class CharT>
|
||||
class basic_string {
|
||||
public:
|
||||
__attribute__((__visibility__("hidden")))
|
||||
const value_type* data() const noexcept { ... }
|
||||
};
|
||||
|
||||
template class basic_string<char>;
|
||||
|
||||
Since an explicit template instantiation declaration for ``basic_string<char>``
|
||||
is provided, the compiler is free to assume that ``basic_string<char>::data()``
|
||||
will be provided by another translation unit, and it is free to produce an
|
||||
external call to this function. However, since ``data()`` has hidden visibility
|
||||
and the explicit template instantiation is provided in a shared library (as
|
||||
opposed to simply another translation unit), ``basic_string<char>::data()``
|
||||
won't be found and a link error will ensue. This happens because the compiler
|
||||
assumes that ``basic_string<char>::data()`` is part of the explicit template
|
||||
instantiation declaration, when it really isn't. To tell the compiler that
|
||||
``data()`` is not part of the explicit template instantiation declaration, the
|
||||
``exclude_from_explicit_instantiation`` attribute can be used:
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
// in <string>
|
||||
template <class CharT>
|
||||
class basic_string {
|
||||
public:
|
||||
__attribute__((__visibility__("hidden")))
|
||||
__attribute__((exclude_from_explicit_instantiation))
|
||||
const value_type* data() const noexcept { ... }
|
||||
};
|
||||
|
||||
template class basic_string<char>;
|
||||
|
||||
Now, the compiler won't assume that ``basic_string<char>::data()`` is provided
|
||||
externally despite there being an explicit template instantiation declaration:
|
||||
the compiler will implicitly instantiate ``basic_string<char>::data()`` in the
|
||||
TUs where it is used.
|
||||
|
||||
This attribute can be used on static and non-static member functions of class
|
||||
templates, static data members of class templates and member classes of class
|
||||
templates.
|
||||
}];
|
||||
}
|
||||
|
||||
def DisableTailCallsDocs : Documentation {
|
||||
let Category = DocCatFunction;
|
||||
let Content = [{
|
||||
|
|
|
@ -644,7 +644,8 @@ void Sema::getUndefinedButUsed(
|
|||
continue;
|
||||
if (FD->isExternallyVisible() &&
|
||||
!isExternalWithNoLinkageType(FD) &&
|
||||
!FD->getMostRecentDecl()->isInlined())
|
||||
!FD->getMostRecentDecl()->isInlined() &&
|
||||
!FD->hasAttr<ExcludeFromExplicitInstantiationAttr>())
|
||||
continue;
|
||||
if (FD->getBuiltinID())
|
||||
continue;
|
||||
|
@ -654,7 +655,8 @@ void Sema::getUndefinedButUsed(
|
|||
continue;
|
||||
if (VD->isExternallyVisible() &&
|
||||
!isExternalWithNoLinkageType(VD) &&
|
||||
!VD->getMostRecentDecl()->isInline())
|
||||
!VD->getMostRecentDecl()->isInline() &&
|
||||
!VD->hasAttr<ExcludeFromExplicitInstantiationAttr>())
|
||||
continue;
|
||||
|
||||
// Skip VarDecls that lack formal definitions but which we know are in
|
||||
|
|
|
@ -6512,6 +6512,9 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D,
|
|||
case ParsedAttr::AT_InternalLinkage:
|
||||
handleInternalLinkageAttr(S, D, AL);
|
||||
break;
|
||||
case ParsedAttr::AT_ExcludeFromExplicitInstantiation:
|
||||
handleSimpleAttribute<ExcludeFromExplicitInstantiationAttr>(S, D, AL);
|
||||
break;
|
||||
case ParsedAttr::AT_LTOVisibilityPublic:
|
||||
handleSimpleAttribute<LTOVisibilityPublicAttr>(S, D, AL);
|
||||
break;
|
||||
|
|
|
@ -2574,10 +2574,14 @@ Sema::InstantiateClassMembers(SourceLocation PointOfInstantiation,
|
|||
for (auto *D : Instantiation->decls()) {
|
||||
bool SuppressNew = false;
|
||||
if (auto *Function = dyn_cast<FunctionDecl>(D)) {
|
||||
if (FunctionDecl *Pattern
|
||||
= Function->getInstantiatedFromMemberFunction()) {
|
||||
MemberSpecializationInfo *MSInfo
|
||||
= Function->getMemberSpecializationInfo();
|
||||
if (FunctionDecl *Pattern =
|
||||
Function->getInstantiatedFromMemberFunction()) {
|
||||
|
||||
if (Function->hasAttr<ExcludeFromExplicitInstantiationAttr>())
|
||||
continue;
|
||||
|
||||
MemberSpecializationInfo *MSInfo =
|
||||
Function->getMemberSpecializationInfo();
|
||||
assert(MSInfo && "No member specialization information?");
|
||||
if (MSInfo->getTemplateSpecializationKind()
|
||||
== TSK_ExplicitSpecialization)
|
||||
|
@ -2618,6 +2622,9 @@ Sema::InstantiateClassMembers(SourceLocation PointOfInstantiation,
|
|||
continue;
|
||||
|
||||
if (Var->isStaticDataMember()) {
|
||||
if (Var->hasAttr<ExcludeFromExplicitInstantiationAttr>())
|
||||
continue;
|
||||
|
||||
MemberSpecializationInfo *MSInfo = Var->getMemberSpecializationInfo();
|
||||
assert(MSInfo && "No member specialization information?");
|
||||
if (MSInfo->getTemplateSpecializationKind()
|
||||
|
@ -2649,6 +2656,9 @@ Sema::InstantiateClassMembers(SourceLocation PointOfInstantiation,
|
|||
}
|
||||
}
|
||||
} else if (auto *Record = dyn_cast<CXXRecordDecl>(D)) {
|
||||
if (Record->hasAttr<ExcludeFromExplicitInstantiationAttr>())
|
||||
continue;
|
||||
|
||||
// Always skip the injected-class-name, along with any
|
||||
// redeclarations of nested classes, since both would cause us
|
||||
// to try to instantiate the members of a class twice.
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
// RUN: %clang_cc1 -triple x86_64-unknown-unknown -emit-llvm -O0 -o - %s | FileCheck %s
|
||||
|
||||
// Test that we do not assume that entities marked with the
|
||||
// exclude_from_explicit_instantiation attribute are instantiated
|
||||
// in another TU when an extern template instantiation declaration
|
||||
// is present. We test that by making sure that definitions are
|
||||
// generated in this TU despite there being an extern template
|
||||
// instantiation declaration, which is normally not the case.
|
||||
|
||||
#define EXCLUDE_FROM_EXPLICIT_INSTANTIATION __attribute__((exclude_from_explicit_instantiation))
|
||||
|
||||
template <class T>
|
||||
struct Foo {
|
||||
EXCLUDE_FROM_EXPLICIT_INSTANTIATION inline void non_static_member_function1();
|
||||
EXCLUDE_FROM_EXPLICIT_INSTANTIATION void non_static_member_function2();
|
||||
|
||||
EXCLUDE_FROM_EXPLICIT_INSTANTIATION static inline void static_member_function1();
|
||||
EXCLUDE_FROM_EXPLICIT_INSTANTIATION static void static_member_function2();
|
||||
|
||||
EXCLUDE_FROM_EXPLICIT_INSTANTIATION static int static_data_member;
|
||||
|
||||
struct EXCLUDE_FROM_EXPLICIT_INSTANTIATION member_class1 {
|
||||
static void static_member_function() { }
|
||||
};
|
||||
|
||||
struct member_class2 {
|
||||
EXCLUDE_FROM_EXPLICIT_INSTANTIATION static void static_member_function() { }
|
||||
};
|
||||
};
|
||||
|
||||
template <class T> inline void Foo<T>::non_static_member_function1() { }
|
||||
template <class T> void Foo<T>::non_static_member_function2() { }
|
||||
|
||||
template <class T> inline void Foo<T>::static_member_function1() { }
|
||||
template <class T> void Foo<T>::static_member_function2() { }
|
||||
|
||||
template <class T> int Foo<T>::static_data_member = 0;
|
||||
|
||||
extern template struct Foo<int>;
|
||||
|
||||
void use() {
|
||||
Foo<int> f;
|
||||
|
||||
// An inline non-static member function marked with the attribute is not
|
||||
// part of the extern template declaration, so a definition must be emitted
|
||||
// in this TU.
|
||||
// CHECK-DAG: define linkonce_odr void @_ZN3FooIiE27non_static_member_function1Ev
|
||||
f.non_static_member_function1();
|
||||
|
||||
// A non-inline non-static member function marked with the attribute is
|
||||
// not part of the extern template declaration, so a definition must be
|
||||
// emitted in this TU.
|
||||
// CHECK-DAG: define linkonce_odr void @_ZN3FooIiE27non_static_member_function2Ev
|
||||
f.non_static_member_function2();
|
||||
|
||||
// An inline static member function marked with the attribute is not
|
||||
// part of the extern template declaration, so a definition must be
|
||||
// emitted in this TU.
|
||||
// CHECK-DAG: define linkonce_odr void @_ZN3FooIiE23static_member_function1Ev
|
||||
Foo<int>::static_member_function1();
|
||||
|
||||
// A non-inline static member function marked with the attribute is not
|
||||
// part of the extern template declaration, so a definition must be
|
||||
// emitted in this TU.
|
||||
// CHECK-DAG: define linkonce_odr void @_ZN3FooIiE23static_member_function2Ev
|
||||
Foo<int>::static_member_function2();
|
||||
|
||||
// A static data member marked with the attribute is not part of the
|
||||
// extern template declaration, so a definition must be emitted in this TU.
|
||||
// CHECK-DAG: @_ZN3FooIiE18static_data_memberE = linkonce_odr global
|
||||
int& odr_use = Foo<int>::static_data_member;
|
||||
|
||||
// A member class marked with the attribute is not part of the extern
|
||||
// template declaration (it is not recursively instantiated), so its member
|
||||
// functions must be emitted in this TU.
|
||||
// CHECK-DAG: define linkonce_odr void @_ZN3FooIiE13member_class122static_member_functionEv
|
||||
Foo<int>::member_class1::static_member_function();
|
||||
|
||||
// A member function marked with the attribute in a member class is not
|
||||
// part of the extern template declaration of the parent class template, so
|
||||
// it must be emitted in this TU.
|
||||
// CHECK-DAG: define linkonce_odr void @_ZN3FooIiE13member_class222static_member_functionEv
|
||||
Foo<int>::member_class2::static_member_function();
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
// The number of supported attributes should never go down!
|
||||
|
||||
// CHECK: #pragma clang attribute supports 128 attributes:
|
||||
// CHECK: #pragma clang attribute supports 129 attributes:
|
||||
// CHECK-NEXT: AMDGPUFlatWorkGroupSize (SubjectMatchRule_function)
|
||||
// CHECK-NEXT: AMDGPUNumSGPR (SubjectMatchRule_function)
|
||||
// CHECK-NEXT: AMDGPUNumVGPR (SubjectMatchRule_function)
|
||||
|
@ -47,6 +47,7 @@
|
|||
// CHECK-NEXT: DisableTailCalls (SubjectMatchRule_function, SubjectMatchRule_objc_method)
|
||||
// CHECK-NEXT: EnableIf (SubjectMatchRule_function)
|
||||
// CHECK-NEXT: EnumExtensibility (SubjectMatchRule_enum)
|
||||
// CHECK-NEXT: ExcludeFromExplicitInstantiation (SubjectMatchRule_variable, SubjectMatchRule_function, SubjectMatchRule_record)
|
||||
// CHECK-NEXT: ExternalSourceSymbol ((SubjectMatchRule_record, SubjectMatchRule_enum, SubjectMatchRule_enum_constant, SubjectMatchRule_field, SubjectMatchRule_function, SubjectMatchRule_namespace, SubjectMatchRule_objc_category, SubjectMatchRule_objc_interface, SubjectMatchRule_objc_method, SubjectMatchRule_objc_property, SubjectMatchRule_objc_protocol, SubjectMatchRule_record, SubjectMatchRule_type_alias, SubjectMatchRule_variable))
|
||||
// CHECK-NEXT: FlagEnum (SubjectMatchRule_enum)
|
||||
// CHECK-NEXT: Flatten (SubjectMatchRule_function)
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
// RUN: %clang_cc1 -fsyntax-only -Wundefined-func-template -Wundefined-var-template -verify %s
|
||||
|
||||
// Test that a diagnostic is emitted when an entity marked with the
|
||||
// exclude_from_explicit_instantiation attribute is not defined in
|
||||
// the current TU but it is used (and it is hence implicitly
|
||||
// instantiated).
|
||||
|
||||
#define EXCLUDE_FROM_EXPLICIT_INSTANTIATION __attribute__((exclude_from_explicit_instantiation))
|
||||
|
||||
template <class T>
|
||||
struct Foo {
|
||||
EXCLUDE_FROM_EXPLICIT_INSTANTIATION void non_static_member_function(); // expected-note{{forward declaration of template entity is here}}
|
||||
EXCLUDE_FROM_EXPLICIT_INSTANTIATION static void static_member_function(); // expected-note{{forward declaration of template entity is here}}
|
||||
EXCLUDE_FROM_EXPLICIT_INSTANTIATION static int static_data_member; // expected-note{{forward declaration of template entity is here}}
|
||||
struct EXCLUDE_FROM_EXPLICIT_INSTANTIATION nested {
|
||||
static int static_member_function(); // expected-note{{forward declaration of template entity is here}}
|
||||
};
|
||||
};
|
||||
|
||||
extern template struct Foo<int>;
|
||||
|
||||
void use() {
|
||||
Foo<int> foo;
|
||||
|
||||
foo.non_static_member_function(); // expected-warning{{instantiation of function 'Foo<int>::non_static_member_function' required here, but no definition is available}}
|
||||
// expected-note@-1 {{add an explicit instantiation}}
|
||||
|
||||
Foo<int>::static_member_function(); // expected-warning{{instantiation of function 'Foo<int>::static_member_function' required here, but no definition is available}}
|
||||
// expected-note@-1 {{add an explicit instantiation}}
|
||||
|
||||
(void)Foo<int>::static_data_member; // expected-warning{{instantiation of variable 'Foo<int>::static_data_member' required here, but no definition is available}}
|
||||
// expected-note@-1 {{add an explicit instantiation}}
|
||||
|
||||
Foo<int>::nested::static_member_function(); // expected-warning{{instantiation of function 'Foo<int>::nested::static_member_function' required here, but no definition is available}}
|
||||
// expected-note@-1 {{add an explicit instantiation}}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
// RUN: %clang_cc1 -fsyntax-only -verify %s
|
||||
|
||||
// Test that explicit instantiations do not instantiate entities
|
||||
// marked with the exclude_from_explicit_instantiation attribute.
|
||||
|
||||
#define EXCLUDE_FROM_EXPLICIT_INSTANTIATION __attribute__((exclude_from_explicit_instantiation))
|
||||
|
||||
template <class T>
|
||||
struct Foo {
|
||||
EXCLUDE_FROM_EXPLICIT_INSTANTIATION inline void non_static_member_function1();
|
||||
|
||||
EXCLUDE_FROM_EXPLICIT_INSTANTIATION void non_static_member_function2();
|
||||
|
||||
EXCLUDE_FROM_EXPLICIT_INSTANTIATION static inline void static_member_function1();
|
||||
|
||||
EXCLUDE_FROM_EXPLICIT_INSTANTIATION static void static_member_function2();
|
||||
|
||||
EXCLUDE_FROM_EXPLICIT_INSTANTIATION static int static_data_member;
|
||||
|
||||
struct EXCLUDE_FROM_EXPLICIT_INSTANTIATION member_class1 {
|
||||
static void non_static_member_function() { using Fail = typename T::fail; }
|
||||
};
|
||||
|
||||
struct member_class2 {
|
||||
EXCLUDE_FROM_EXPLICIT_INSTANTIATION static void non_static_member_function() { using Fail = typename T::fail; }
|
||||
};
|
||||
};
|
||||
|
||||
template <class T>
|
||||
inline void Foo<T>::non_static_member_function1() { using Fail = typename T::fail; }
|
||||
|
||||
template <class T>
|
||||
void Foo<T>::non_static_member_function2() { using Fail = typename T::fail; }
|
||||
|
||||
template <class T>
|
||||
inline void Foo<T>::static_member_function1() { using Fail = typename T::fail; }
|
||||
|
||||
template <class T>
|
||||
void Foo<T>::static_member_function2() { using Fail = typename T::fail; }
|
||||
|
||||
template <class T>
|
||||
int Foo<T>::static_data_member = T::fail;
|
||||
|
||||
// expected-no-diagnostics
|
||||
template struct Foo<int>;
|
|
@ -0,0 +1,69 @@
|
|||
// RUN: %clang_cc1 -Wno-unused-local-typedef -fsyntax-only -verify %s
|
||||
|
||||
// Test that extern instantiation declarations cause members marked with
|
||||
// the exclude_from_explicit_instantiation attribute to be instantiated in
|
||||
// the current TU.
|
||||
|
||||
#define EXCLUDE_FROM_EXPLICIT_INSTANTIATION __attribute__((exclude_from_explicit_instantiation))
|
||||
|
||||
template <class T>
|
||||
struct Foo {
|
||||
EXCLUDE_FROM_EXPLICIT_INSTANTIATION inline void non_static_member_function1();
|
||||
|
||||
EXCLUDE_FROM_EXPLICIT_INSTANTIATION void non_static_member_function2();
|
||||
|
||||
EXCLUDE_FROM_EXPLICIT_INSTANTIATION static inline void static_member_function1();
|
||||
|
||||
EXCLUDE_FROM_EXPLICIT_INSTANTIATION static void static_member_function2();
|
||||
|
||||
EXCLUDE_FROM_EXPLICIT_INSTANTIATION static int static_data_member;
|
||||
|
||||
struct EXCLUDE_FROM_EXPLICIT_INSTANTIATION member_class1 {
|
||||
static void static_member_function() {
|
||||
using Fail = typename T::invalid; // expected-error{{no type named 'invalid' in 'Empty'}}
|
||||
}
|
||||
};
|
||||
|
||||
struct member_class2 {
|
||||
EXCLUDE_FROM_EXPLICIT_INSTANTIATION static void static_member_function() {
|
||||
using Fail = typename T::invalid; // expected-error{{no type named 'invalid' in 'Empty'}}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
template <class T>
|
||||
inline void Foo<T>::non_static_member_function1() {
|
||||
using Fail = typename T::invalid; // expected-error{{no type named 'invalid' in 'Empty'}}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Foo<T>::non_static_member_function2() {
|
||||
using Fail = typename T::invalid; // expected-error{{no type named 'invalid' in 'Empty'}}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline void Foo<T>::static_member_function1() {
|
||||
using Fail = typename T::invalid; // expected-error{{no type named 'invalid' in 'Empty'}}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Foo<T>::static_member_function2() {
|
||||
using Fail = typename T::invalid; // expected-error{{no type named 'invalid' in 'Empty'}}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
int Foo<T>::static_data_member = T::invalid; // expected-error{{no member named 'invalid' in 'Empty'}}
|
||||
|
||||
struct Empty { };
|
||||
extern template struct Foo<Empty>;
|
||||
|
||||
int main() {
|
||||
Foo<Empty> foo;
|
||||
foo.non_static_member_function1(); // expected-note{{in instantiation of}}
|
||||
foo.non_static_member_function2(); // expected-note{{in instantiation of}}
|
||||
Foo<Empty>::static_member_function1(); // expected-note{{in instantiation of}}
|
||||
Foo<Empty>::static_member_function2(); // expected-note{{in instantiation of}}
|
||||
(void)foo.static_data_member; // expected-note{{in instantiation of}}
|
||||
Foo<Empty>::member_class1::static_member_function(); // expected-note{{in instantiation of}}
|
||||
Foo<Empty>::member_class2::static_member_function(); // expected-note{{in instantiation of}}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
// RUN: %clang_cc1 -fsyntax-only -verify %s
|
||||
|
||||
// Test that we properly merge the exclude_from_explicit_instantiation
|
||||
// attribute on redeclarations.
|
||||
|
||||
#define EXCLUDE_FROM_EXPLICIT_INSTANTIATION __attribute__((exclude_from_explicit_instantiation))
|
||||
|
||||
template <class T>
|
||||
struct Foo {
|
||||
// Declaration without the attribute, definition with the attribute.
|
||||
void func1();
|
||||
|
||||
// Declaration with the attribute, definition without the attribute.
|
||||
EXCLUDE_FROM_EXPLICIT_INSTANTIATION void func2();
|
||||
|
||||
// Declaration with the attribute, definition with the attribute.
|
||||
EXCLUDE_FROM_EXPLICIT_INSTANTIATION void func3();
|
||||
};
|
||||
|
||||
template <class T>
|
||||
EXCLUDE_FROM_EXPLICIT_INSTANTIATION void Foo<T>::func1() {
|
||||
using Fail = typename T::invalid; // expected-error{{no type named 'invalid' in 'Empty'}}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Foo<T>::func2() {
|
||||
using Fail = typename T::invalid; // expected-error{{no type named 'invalid' in 'Empty'}}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
EXCLUDE_FROM_EXPLICIT_INSTANTIATION void Foo<T>::func3() {
|
||||
using Fail = typename T::invalid; // expected-error{{no type named 'invalid' in 'Empty'}}
|
||||
}
|
||||
|
||||
struct Empty { };
|
||||
extern template struct Foo<Empty>;
|
||||
|
||||
int main() {
|
||||
Foo<Empty> foo;
|
||||
foo.func1(); // expected-note{{in instantiation of}}
|
||||
foo.func2(); // expected-note{{in instantiation of}}
|
||||
foo.func3(); // expected-note{{in instantiation of}}
|
||||
}
|
Loading…
Reference in New Issue