Given the following C++20 code:
struct foo_t
{
int a = 3;
};
template <foo_t t> struct bar_t
{
bool is_default = t.a == 3;
};
int
main()
{
constexpr auto foo = foo_t{};
bar_t<foo> bar;
return 0;
}
If we dump the clang AST (from LLVM-18) using clang -std=c++20 -Xclang -ast-dump foo.cpp
we get the following for the instantiation of bar_t
(truncated):
| `-ClassTemplateSpecializationDecl 0xc2b1b18 <line:6:1, line:9:1> line:6:27 struct bar_t definition implicit_instantiation
| |-DefinitionData pass_in_registers aggregate standard_layout trivially_copyable literal has_constexpr_non_copy_move_ctor can_const_default_init
| | |-[...]
| |-TemplateArgument decl
| | `-TemplateParamObject 0xc2b1a88 '' 'const foo_t'
| |-CXXRecordDecl 0xc2b1dd0 <col:20, col:27> col:27 implicit struct bar_t
| |-FieldDecl 0xc2b1e80 <line:8:3, col:28> col:8 is_default 'bool'
| | `-BinaryOperator 0xc2b3c20 <col:21, col:28> 'bool' '=='
| | |-ImplicitCastExpr 0xc2b3c08 <col:21, col:23> 'int' <LValueToRValue>
| | | `-MemberExpr 0xc2b3bd8 <col:21, col:23> 'const int' lvalue .a 0xc2924c8
| | | `-SubstNonTypeTemplateParmExpr 0xc2b3bb0 <col:21> 'const foo_t' lvalue
| | | |-NonTypeTemplateParmDecl 0xc2925c8 <line:6:11, col:17> col:17 referenced 'foo_t' depth 0 index 0 t
| | | `-DeclRefExpr 0xc2b3b90 <line:8:21> 'const foo_t' lvalue TemplateParamObject 0xc2b1a88 '' 'const foo_t'
| | `-IntegerLiteral 0xc292b50 <col:28> 'int' 3
| |-[...]
As you can see, there is no name for the TemplateParamObject
object, although it inherits from NamedDecl
(see here).
Is there a way to add a name to this declaration?
If so, what does it refer to ? (I'm guessing it would refer to the name of the object at the instantiation point in clang's abstract interpreter necessary for constant evaluation)
If not, why is it a NamedDecl
?
In Clang 18, the name in
TemplateParamObjectDecl
is always empty; it does not refer to anything.
TemplateParamObjectDecl
?TemplateParamObjectDecl
is used to represent a value of user-defined
type that is passed as a template argument to a parameter with
user-defined type. (IMO, it should have been called an "argument", not
a "parameter", but oh well.) When the same argument value is used in
multiple places in a translation unit, they all share the same
TemplateParamObjectDecl
AST node.
For example (adapted from the one in the documentation):
struct A { int x, y; };
// This template is instantiated twice, but both instantiations use the
// same `TemplateParamObjectDecl` for the `A` argument.
template <A a, int i>
struct S {};
S<A{1, 2}, 3> s3;
S<A{1, 2}, 4> s4;
The output of clang++ -fsyntax-only -Xclang -ast-dump -std=c++20
has:
| |-ClassTemplateSpecializationDecl 0x562f0fa3ddc0 <line:8:1, line:9:11> col:8 struct S definition implicit_instantiation
[...]
| | |-TemplateArgument decl
| | | `-TemplateParamObject 0x562f0fa3dd10 '' 'const A' <--- SAME
[...]
| `-ClassTemplateSpecializationDecl 0x562f0fa5cae8 <line:8:1, line:9:11> col:8 struct S definition implicit_instantiation
[...]
| |-TemplateArgument decl
| | `-TemplateParamObject 0x562f0fa3dd10 '' 'const A' <--- SAME
When such a value appears as a
TemplateArgument
,
it is (as of Clang 18) represented using an ArgKind
of
Declaration
and a pointer to a
ValueDecl
,
retrievable by calling
getAsDecl()
.
Note: There is a comment on the StructuralValue
enumerator of
ArgKind
indicating the representation may change in the near future.
NamedDecl
?If we look at the
blame output for DeclTemplate.h
,
we can see that TemplateParamObjectDecl
was added in commit
ba4768c966
.
The title of that commit refers to two language change proposals,
P0732R2
and
P1907R1,
but neither proposal seems to motivate Clang's implementation, and I
don't see any other explicit rationale, so a little speculation is
required.
In C++17 and earlier, non-type template parameters could have one of the following types (quoting [temp.param] p4):
std::nullptr_t
, orThe arguments corresponding to parameter types (4.2), (4.3), and (4.4)
are represented in Clang as a pointer to a ValueDecl
because each of
the pointees is an entity declared by such a declaration. When
class-typed template arguments were added, it was evidently seen as
desirable (or perhaps merely expedient) to extend that mechanism and
also represent them using a declaration pointer. (I suspect this choice
helps with name mangling and linker uniquification.)
Consequently, TemplateParamObjectDecl
, a synthetic anonymous
declaration representing a class-typed template argument, was created.
Since it had to inherit ValueDecl
, that meant also inheriting
NamedDecl
, even though it does not have a name.
In your example program, the class-typed argument value is stored in a
variable (foo
), but that name plays no role here.
The question asks "Is there a way to add a name to this declaration?", but despite clarification in the comment I still don't really understand the goal. In any case, the answer seems to be "No", at least per the Clang design intent. I suppose you could try setting its name, but I'd be worried about breaking assumptions elsewhere by doing so, especially if you let the code generator run afterward.
However (as noted in the comment), in each place where a
TemplateParamObjectDecl
pointer appears, it will be as the argument to
a particular template parameter, and that parameter may have a name (in
your example, it is called t
).
Relatedly, in my own work on a refactoring tool, I have encountered
situations where I need to have a name for template parameters that were
anonymous in the original code, and have not encountered any problems by
just setting the name of
TemplateTypeParmDecl
,
etc.