Skip to content

Commit 85043c1

Browse files
[Clang] Add a builtin that deduplicate types into a pack (#106730)
The new builtin `__builtin_dedup_pack` removes duplicates from list of types. The added builtin is special in that they produce an unexpanded pack in the spirit of P3115R0 proposal. Produced packs can be used directly in template argument lists and get immediately expanded as soon as results of the computation are available. It allows to easily combine them, e.g.: ```cpp template <class ...T> struct Normalize { // Note: sort is not included in this PR, it illustrates the idea. using result = std::tuple< __builtin_sort_pack< __builtin_dedup_pack<int, double, T...>... >...>; } ; ``` Limitations: - only supported in template arguments and bases, - can only be used inside the templates, even if non-dependent, - the builtins cannot be assigned to template template parameters. The actual implementation proceeds as follows: - When the compiler encounters a `__builtin_dedup_pack` or other type-producing builtin with dependent arguments, it creates a dependent `TemplateSpecializationType`. - During substitution, if the template arguments are non-dependent, we will produce: a new type `SubstBuiltinTemplatePackType`, which stores an argument pack that needs to be substituted. This type is similar to the existing `SubstTemplateParmPack` in that it carries the argument pack that needs to be expanded further. The relevant code is shared. - On top of that, Clang also wraps the resulting type into `TemplateSpecializationType`, but this time only as a sugar. - To actually expand those packs, we collect the produced `SubstBuiltinTemplatePackType` inside `CollectUnexpandedPacks`. Because we know the size of the produces packs only after the initial substitution, places that do the actual expansion will need to have a second run over the substituted type to finalize the expansions (in this patch we only support this for template arguments, see `ExpandTemplateArgument`). If the expansion are requested in the places we do not currently support, we will produce an error. More follow-up work will be needed to fully shape this: - adding the builtin that sorts types, - remove the restrictions for expansions, - implementing P3115R0 (scheduled for C++29, see cplusplus/papers#2300).
1 parent 2b7b8bd commit 85043c1

40 files changed

+1238
-237
lines changed

clang-tools-extra/clangd/unittests/FindTargetTests.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,12 @@ TEST_F(TargetDeclTest, BuiltinTemplates) {
731731
using type_pack_element = [[__type_pack_element]]<N, Pack...>;
732732
)cpp";
733733
EXPECT_DECLS("TemplateSpecializationTypeLoc", );
734+
735+
Code = R"cpp(
736+
template <template <class...> class Templ, class... Types>
737+
using dedup_types = Templ<[[__builtin_dedup_pack]]<Types...>...>;
738+
)cpp";
739+
EXPECT_DECLS("TemplateSpecializationTypeLoc", );
734740
}
735741

736742
TEST_F(TargetDeclTest, MemberOfTemplate) {

clang/docs/LanguageExtensions.rst

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1810,6 +1810,37 @@ __make_integer_seq
18101810

18111811
This alias returns ``IntSeq`` instantiated with ``IntSeqT = T``and ``Ints`` being the pack ``0, ..., N - 1``.
18121812

1813+
__builtin_dedup_pack
1814+
--------------------
1815+
1816+
.. code-block:: c++
1817+
1818+
template <class... Ts>
1819+
using __builtin_dedup_pack = ...;
1820+
1821+
This alias takes a template parameter pack ``Ts`` and produces a new unexpanded pack containing the unique types
1822+
from ``Ts``, with the order of the first occurrence of each type preserved.
1823+
It is useful in template metaprogramming to normalize type lists.
1824+
1825+
The resulting pack can be expanded in contexts like template argument lists or base specifiers.
1826+
1827+
**Example of Use**:
1828+
1829+
.. code-block:: c++
1830+
1831+
template <typename...> struct TypeList;
1832+
1833+
// The resulting type is TypeList<int, double, char>
1834+
template <typename ...ExtraTypes>
1835+
using MyTypeList = TypeList<__builtin_dedup_pack<int, double, int, char, double, ExtraTypes...>...>;
1836+
1837+
**Limitations**:
1838+
1839+
* This builtin can only be used inside a template.
1840+
* The resulting pack is currently only supported for expansion in template argument lists and base specifiers.
1841+
* This builtin cannot be assigned to a template template parameter.
1842+
1843+
18131844
Type Trait Primitives
18141845
=====================
18151846

clang/docs/ReleaseNotes.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,22 @@ Non-comprehensive list of changes in this release
145145
correct method to check for these features is to test for the ``__PTRAUTH__``
146146
macro.
147147

148+
- Added a new builtin, ``__builtin_dedup_pack``, to remove duplicate types from a parameter pack.
149+
This feature is particularly useful in template metaprogramming for normalizing type lists.
150+
The builtin produces a new, unexpanded parameter pack that can be used in contexts like template
151+
argument lists or base specifiers.
152+
153+
.. code-block:: c++
154+
155+
template <typename...> struct TypeList;
156+
157+
// The resulting type is TypeList<int, double, char>
158+
using MyTypeList = TypeList<__builtin_dedup_pack<int, double, int, char, double>...>;
159+
160+
Currently, the use of ``__builtin_dedup_pack`` is limited to template arguments and base
161+
specifiers, it also must be used within a template context.
162+
163+
148164
New Compiler Flags
149165
------------------
150166
- New option ``-fno-sanitize-annotate-debug-info-traps`` added to disable emitting trap reasons into the debug info when compiling with trapping UBSan (e.g. ``-fsanitize-trap=undefined``).

clang/include/clang/AST/ASTContext.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,8 @@ class ASTContext : public RefCountedBase<ASTContext> {
230230
SubstTemplateTypeParmTypes;
231231
mutable llvm::FoldingSet<SubstTemplateTypeParmPackType>
232232
SubstTemplateTypeParmPackTypes;
233+
mutable llvm::FoldingSet<SubstBuiltinTemplatePackType>
234+
SubstBuiltinTemplatePackTypes;
233235
mutable llvm::ContextualFoldingSet<TemplateSpecializationType, ASTContext&>
234236
TemplateSpecializationTypes;
235237
mutable llvm::FoldingSet<ParenType> ParenTypes{GeneralTypesLog2InitSize};
@@ -1895,6 +1897,7 @@ class ASTContext : public RefCountedBase<ASTContext> {
18951897
QualType getSubstTemplateTypeParmPackType(Decl *AssociatedDecl,
18961898
unsigned Index, bool Final,
18971899
const TemplateArgument &ArgPack);
1900+
QualType getSubstBuiltinTemplatePack(const TemplateArgument &ArgPack);
18981901

18991902
QualType
19001903
getTemplateTypeParmType(unsigned Depth, unsigned Index,

clang/include/clang/AST/DeclTemplate.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1796,7 +1796,10 @@ class BuiltinTemplateDecl : public TemplateDecl {
17961796
}
17971797

17981798
BuiltinTemplateKind getBuiltinTemplateKind() const { return BTK; }
1799+
1800+
bool isPackProducingBuiltinTemplate() const;
17991801
};
1802+
bool isPackProducingBuiltinTemplateName(TemplateName N);
18001803

18011804
/// Provides information about an explicit instantiation of a variable or class
18021805
/// template.

clang/include/clang/AST/RecursiveASTVisitor.h

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,8 @@ template <typename Derived> class RecursiveASTVisitor {
492492
bool TraverseTemplateArgumentLocsHelper(const TemplateArgumentLoc *TAL,
493493
unsigned Count);
494494
bool TraverseArrayTypeLocHelper(ArrayTypeLoc TL);
495+
bool TraverseSubstPackTypeHelper(SubstPackType *T);
496+
bool TraverseSubstPackTypeLocHelper(SubstPackTypeLoc TL);
495497
bool TraverseRecordHelper(RecordDecl *D);
496498
bool TraverseCXXRecordHelper(CXXRecordDecl *D);
497499
bool TraverseDeclaratorHelper(DeclaratorDecl *D);
@@ -1138,9 +1140,10 @@ DEF_TRAVERSE_TYPE(TemplateTypeParmType, {})
11381140
DEF_TRAVERSE_TYPE(SubstTemplateTypeParmType, {
11391141
TRY_TO(TraverseType(T->getReplacementType()));
11401142
})
1141-
DEF_TRAVERSE_TYPE(SubstTemplateTypeParmPackType, {
1142-
TRY_TO(TraverseTemplateArgument(T->getArgumentPack()));
1143-
})
1143+
DEF_TRAVERSE_TYPE(SubstTemplateTypeParmPackType,
1144+
{ TRY_TO(TraverseSubstPackTypeHelper(T)); })
1145+
DEF_TRAVERSE_TYPE(SubstBuiltinTemplatePackType,
1146+
{ TRY_TO(TraverseSubstPackTypeHelper(T)); })
11441147

11451148
DEF_TRAVERSE_TYPE(AttributedType,
11461149
{ TRY_TO(TraverseType(T->getModifiedType())); })
@@ -1481,9 +1484,26 @@ DEF_TRAVERSE_TYPELOC(TemplateTypeParmType, {})
14811484
DEF_TRAVERSE_TYPELOC(SubstTemplateTypeParmType, {
14821485
TRY_TO(TraverseType(TL.getTypePtr()->getReplacementType()));
14831486
})
1484-
DEF_TRAVERSE_TYPELOC(SubstTemplateTypeParmPackType, {
1487+
1488+
template <typename Derived>
1489+
bool RecursiveASTVisitor<Derived>::TraverseSubstPackTypeLocHelper(
1490+
SubstPackTypeLoc TL) {
14851491
TRY_TO(TraverseTemplateArgument(TL.getTypePtr()->getArgumentPack()));
1486-
})
1492+
return true;
1493+
}
1494+
1495+
template <typename Derived>
1496+
bool RecursiveASTVisitor<Derived>::TraverseSubstPackTypeHelper(
1497+
SubstPackType *T) {
1498+
TRY_TO(TraverseTemplateArgument(T->getArgumentPack()));
1499+
return true;
1500+
}
1501+
1502+
DEF_TRAVERSE_TYPELOC(SubstTemplateTypeParmPackType,
1503+
{ TRY_TO(TraverseSubstPackTypeLocHelper(TL)); })
1504+
1505+
DEF_TRAVERSE_TYPELOC(SubstBuiltinTemplatePackType,
1506+
{ TRY_TO(TraverseSubstPackTypeLocHelper(TL)); })
14871507

14881508
DEF_TRAVERSE_TYPELOC(ParenType, { TRY_TO(TraverseTypeLoc(TL.getInnerLoc())); })
14891509

clang/include/clang/AST/Type.h

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2210,20 +2210,24 @@ class alignas(TypeAlignment) Type : public ExtQualsTypeCommonBase {
22102210
unsigned PackIndex : 15;
22112211
};
22122212

2213-
class SubstTemplateTypeParmPackTypeBitfields {
2213+
class SubstPackTypeBitfields {
2214+
friend class SubstPackType;
22142215
friend class SubstTemplateTypeParmPackType;
22152216

22162217
LLVM_PREFERRED_TYPE(TypeBitfields)
22172218
unsigned : NumTypeBits;
22182219

2219-
// The index of the template parameter this substitution represents.
2220-
unsigned Index : 16;
2221-
22222220
/// The number of template arguments in \c Arguments, which is
22232221
/// expected to be able to hold at least 1024 according to [implimits].
22242222
/// However as this limit is somewhat easy to hit with template
22252223
/// metaprogramming we'd prefer to keep it as large as possible.
22262224
unsigned NumArgs : 16;
2225+
2226+
// The index of the template parameter this substitution represents.
2227+
// Only used by SubstTemplateTypeParmPackType. We keep it in the same
2228+
// class to avoid dealing with complexities of bitfields that go over
2229+
// the size of `unsigned`.
2230+
unsigned SubstTemplTypeParmPackIndex : 16;
22272231
};
22282232

22292233
class TemplateSpecializationTypeBitfields {
@@ -2340,7 +2344,7 @@ class alignas(TypeAlignment) Type : public ExtQualsTypeCommonBase {
23402344
VectorTypeBitfields VectorTypeBits;
23412345
TemplateTypeParmTypeBitfields TemplateTypeParmTypeBits;
23422346
SubstTemplateTypeParmTypeBitfields SubstTemplateTypeParmTypeBits;
2343-
SubstTemplateTypeParmPackTypeBitfields SubstTemplateTypeParmPackTypeBits;
2347+
SubstPackTypeBitfields SubstPackTypeBits;
23442348
TemplateSpecializationTypeBitfields TemplateSpecializationTypeBits;
23452349
DependentTemplateSpecializationTypeBitfields
23462350
DependentTemplateSpecializationTypeBits;
@@ -6992,6 +6996,56 @@ class SubstTemplateTypeParmType final
69926996
}
69936997
};
69946998

6999+
/// Represents the result of substituting a set of types as a template argument
7000+
/// that needs to be expanded later.
7001+
///
7002+
/// These types are always dependent and produced depending on the situations:
7003+
/// - SubstTemplateTypeParmPack is an expansion that had to be delayed,
7004+
/// - SubstBuiltinTemplatePackType is an expansion from a builtin.
7005+
class SubstPackType : public Type, public llvm::FoldingSetNode {
7006+
friend class ASTContext;
7007+
7008+
/// A pointer to the set of template arguments that this
7009+
/// parameter pack is instantiated with.
7010+
const TemplateArgument *Arguments;
7011+
7012+
protected:
7013+
SubstPackType(TypeClass Derived, QualType Canon,
7014+
const TemplateArgument &ArgPack);
7015+
7016+
public:
7017+
unsigned getNumArgs() const { return SubstPackTypeBits.NumArgs; }
7018+
7019+
TemplateArgument getArgumentPack() const;
7020+
7021+
void Profile(llvm::FoldingSetNodeID &ID);
7022+
static void Profile(llvm::FoldingSetNodeID &ID,
7023+
const TemplateArgument &ArgPack);
7024+
7025+
static bool classof(const Type *T) {
7026+
return T->getTypeClass() == SubstTemplateTypeParmPack ||
7027+
T->getTypeClass() == SubstBuiltinTemplatePack;
7028+
}
7029+
};
7030+
7031+
/// Represents the result of substituting a builtin template as a pack.
7032+
class SubstBuiltinTemplatePackType : public SubstPackType {
7033+
friend class ASTContext;
7034+
7035+
SubstBuiltinTemplatePackType(QualType Canon, const TemplateArgument &ArgPack);
7036+
7037+
public:
7038+
bool isSugared() const { return false; }
7039+
QualType desugar() const { return QualType(this, 0); }
7040+
7041+
/// Mark that we reuse the Profile. We do not introduce new fields.
7042+
using SubstPackType::Profile;
7043+
7044+
static bool classof(const Type *T) {
7045+
return T->getTypeClass() == SubstBuiltinTemplatePack;
7046+
}
7047+
};
7048+
69957049
/// Represents the result of substituting a set of types for a template
69967050
/// type parameter pack.
69977051
///
@@ -7004,7 +7058,7 @@ class SubstTemplateTypeParmType final
70047058
/// that pack expansion (e.g., when all template parameters have corresponding
70057059
/// arguments), this type will be replaced with the \c SubstTemplateTypeParmType
70067060
/// at the current pack substitution index.
7007-
class SubstTemplateTypeParmPackType : public Type, public llvm::FoldingSetNode {
7061+
class SubstTemplateTypeParmPackType : public SubstPackType {
70087062
friend class ASTContext;
70097063

70107064
/// A pointer to the set of template arguments that this
@@ -7030,21 +7084,17 @@ class SubstTemplateTypeParmPackType : public Type, public llvm::FoldingSetNode {
70307084

70317085
/// Returns the index of the replaced parameter in the associated declaration.
70327086
/// This should match the result of `getReplacedParameter()->getIndex()`.
7033-
unsigned getIndex() const { return SubstTemplateTypeParmPackTypeBits.Index; }
7087+
unsigned getIndex() const {
7088+
return SubstPackTypeBits.SubstTemplTypeParmPackIndex;
7089+
}
70347090

70357091
// This substitution will be Final, which means the substitution will be fully
70367092
// sugared: it doesn't need to be resugared later.
70377093
bool getFinal() const;
70387094

7039-
unsigned getNumArgs() const {
7040-
return SubstTemplateTypeParmPackTypeBits.NumArgs;
7041-
}
7042-
70437095
bool isSugared() const { return false; }
70447096
QualType desugar() const { return QualType(this, 0); }
70457097

7046-
TemplateArgument getArgumentPack() const;
7047-
70487098
void Profile(llvm::FoldingSetNodeID &ID);
70497099
static void Profile(llvm::FoldingSetNodeID &ID, const Decl *AssociatedDecl,
70507100
unsigned Index, bool Final,
@@ -7279,9 +7329,7 @@ class TemplateSpecializationType : public TypeWithKeyword,
72797329
TemplateSpecializationTypeBits.NumArgs};
72807330
}
72817331

7282-
bool isSugared() const {
7283-
return !isDependentType() || isCurrentInstantiation() || isTypeAlias();
7284-
}
7332+
bool isSugared() const;
72857333

72867334
QualType desugar() const {
72877335
return isTypeAlias() ? getAliasedType() : getCanonicalTypeInternal();

clang/include/clang/AST/TypeLoc.h

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -989,12 +989,22 @@ class SubstTemplateTypeParmTypeLoc :
989989
SubstTemplateTypeParmType> {
990990
};
991991

992-
/// Wrapper for substituted template type parameters.
993-
class SubstTemplateTypeParmPackTypeLoc :
994-
public InheritingConcreteTypeLoc<TypeSpecTypeLoc,
995-
SubstTemplateTypeParmPackTypeLoc,
996-
SubstTemplateTypeParmPackType> {
997-
};
992+
/// Abstract type representing delayed type pack expansions.
993+
class SubstPackTypeLoc
994+
: public InheritingConcreteTypeLoc<TypeSpecTypeLoc, SubstPackTypeLoc,
995+
SubstPackType> {};
996+
997+
/// Wrapper for substituted template type parameters.
998+
class SubstTemplateTypeParmPackTypeLoc
999+
: public InheritingConcreteTypeLoc<SubstPackTypeLoc,
1000+
SubstTemplateTypeParmPackTypeLoc,
1001+
SubstTemplateTypeParmPackType> {};
1002+
1003+
/// Wrapper for substituted template type parameters.
1004+
class SubstBuiltinTemplatePackTypeLoc
1005+
: public InheritingConcreteTypeLoc<SubstPackTypeLoc,
1006+
SubstBuiltinTemplatePackTypeLoc,
1007+
SubstBuiltinTemplatePackType> {};
9981008

9991009
struct AttributedLocInfo {
10001010
const Attr *TypeAttr;

clang/include/clang/AST/TypeProperties.td

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -820,26 +820,33 @@ let Class = PackExpansionType in {
820820
}]>;
821821
}
822822

823+
let Class = SubstPackType in {
824+
def : Property<"replacementPack", TemplateArgument> {
825+
let Read = [{ node->getArgumentPack() }];
826+
}
827+
}
828+
823829
let Class = SubstTemplateTypeParmPackType in {
824830
def : Property<"associatedDecl", DeclRef> {
825831
let Read = [{ node->getAssociatedDecl() }];
826832
}
827833
def : Property<"Index", UInt32> {
828834
let Read = [{ node->getIndex() }];
829835
}
830-
def : Property<"Final", Bool> {
831-
let Read = [{ node->getFinal() }];
832-
}
833-
def : Property<"replacementPack", TemplateArgument> {
834-
let Read = [{ node->getArgumentPack() }];
835-
}
836+
def : Property<"Final", Bool> { let Read = [{ node->getFinal() }]; }
836837

837838
def : Creator<[{
838839
return ctx.getSubstTemplateTypeParmPackType(
839840
associatedDecl, Index, Final, replacementPack);
840841
}]>;
841842
}
842843

844+
let Class = SubstBuiltinTemplatePackType in {
845+
def : Creator<[{
846+
return ctx.getSubstBuiltinTemplatePack(replacementPack);
847+
}]>;
848+
}
849+
843850
let Class = BuiltinType in {
844851
def : Property<"kind", BuiltinTypeKind> {
845852
let Read = [{ node->getKind() }];

clang/include/clang/Basic/BuiltinTemplates.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,7 @@ def __builtin_common_type : CPlusPlusBuiltinTemplate<
6262
// typename ...Operands>
6363
def __hlsl_spirv_type : HLSLBuiltinTemplate<
6464
[Uint32T, Uint32T, Uint32T, Class<"Operands", /*is_variadic=*/1>]>;
65+
66+
// template <class ...Args>
67+
def __builtin_dedup_pack
68+
: CPlusPlusBuiltinTemplate<[Class<"Args", /*is_variadic=*/1>]>;

0 commit comments

Comments
 (0)