Skip to content

Commit

Permalink
Ensure template classes are instantiated in correct order
Browse files Browse the repository at this point in the history
- If it depends on a base class, the base needs to be initialized first
- Not all template classes exist in the header (in particular, CRTP) so
  anything not found in the current header gets initialized first
  • Loading branch information
virtuald committed Dec 28, 2023
1 parent 46c5d1e commit e6449d0
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 18 deletions.
34 changes: 22 additions & 12 deletions robotpy_build/autowrap/cxxparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -1987,19 +1987,29 @@ def parse_header(
if doc_add:
doc_add = f"\n{doc_add}"

hctx.template_instances.append(
TemplateInstanceContext(
scope_var=visitor._get_module_var(tmpl_data),
var_name=f"tmplCls{i}",
py_name=k,
full_cpp_name_identifier=qualname,
binder_typename=f"bind_{qualname}_{i}",
params=tmpl_data.params,
header_name=f"{qualname}.hpp",
doc_set=visitor._quote_doc(tmpl_data.doc),
doc_add=visitor._quote_doc(doc_add),
)
tctx = TemplateInstanceContext(
scope_var=visitor._get_module_var(tmpl_data),
var_name=f"tmplCls{i}",
py_name=k,
full_cpp_name_identifier=qualname,
binder_typename=f"bind_{qualname}_{i}",
params=tmpl_data.params,
header_name=f"{qualname}.hpp",
doc_set=visitor._quote_doc(tmpl_data.doc),
doc_add=visitor._quote_doc(doc_add),
)
hctx.template_instances.append(tctx)

# Ensure that template instances are created in class order if the
# template class is in this header file
# - not matching here is not an error
qualname_match = tmpl_data.qualname.lstrip(":")
for cctx in hctx.classes:
if cctx.dep_cpp_name.lstrip(":") == qualname_match:
assert cctx.template
tctx.matched = True
cctx.template.instances.append(tctx)
break

for param in tmpl_data.params:
visitor._add_user_type_caster(param)
Expand Down
24 changes: 18 additions & 6 deletions robotpy_build/autowrap/header.cpp.j2
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,19 @@ struct rpybuild_{{ hname }}_initializer {
{% endfor %}

{# template decls #}
{% for tmpl_data in template_instances %}
{% for tmpl_data in template_instances if not tmpl_data.matched %}
rpygen::{{ tmpl_data.binder_typename }} {{ tmpl_data.var_name }};
{% endfor %}

{# class decls #}
{%- for cls in classes if not cls.template %}
{{ pybind11.cls_decl(cls) }}
{%- for cls in classes %}
{% if not cls.template -%}
{{ pybind11.cls_decl(cls) }}
{%- else -%}
{%- for tmpl_data in cls.template.instances %}
rpygen::{{ tmpl_data.binder_typename }} {{ tmpl_data.var_name }};
{% endfor -%}
{%- endif -%}
{% endfor %}

py::module &m;
Expand All @@ -86,12 +92,18 @@ struct rpybuild_{{ hname }}_initializer {
enum{{ loop.index }}{{ pybind11.enum_init(enum.scope_var, enum) }},
{% endfor %}

{% for tmpl_data in template_instances %}
{% for tmpl_data in template_instances if not tmpl_data.matched %}
{{ tmpl_data.var_name }}({{ tmpl_data.scope_var }}, "{{ tmpl_data.py_name }}"),
{% endfor %}

{% for cls in classes if not cls.template %}
{{ pybind11.cls_init(cls, '"' + cls.py_name + '"') }}
{% for cls in classes %}
{% if not cls.template -%}
{{ pybind11.cls_init(cls, '"' + cls.py_name + '"') }}
{%- else -%}
{%- for tmpl_data in cls.template.instances %}
{{ tmpl_data.var_name }}({{ tmpl_data.scope_var }}, "{{ tmpl_data.py_name }}"),
{% endfor -%}
{%- endif %}
{% endfor %}

m(m)
Expand Down
6 changes: 6 additions & 0 deletions robotpy_build/autowrap/j2_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,9 @@ class ClassTemplateData:
#: the specified C++ code is inserted into the template definition
inline_code: str

#: Instances of this class
instances: typing.List["TemplateInstanceContext"] = field(default_factory=list)


@dataclass
class ClassContext:
Expand Down Expand Up @@ -493,6 +496,9 @@ class TemplateInstanceContext:
doc_set: Documentation
doc_add: Documentation

#: If true, instantiated in class order
matched: bool = False


@dataclass
class HeaderContext:
Expand Down
11 changes: 11 additions & 0 deletions tests/cpp/gen/ft/tdependent_base.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
classes:
TDBase:
TDChild:
template_params:
- T

templates:
TDChild:
qualname: TDChild
params:
- int
1 change: 1 addition & 0 deletions tests/cpp/pyproject.toml.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ generate = [
{tvchild = "templates/tvchild.h"},

{tbasic = "templates/basic.h"},
{tdependent_base = "templates/dependent_base.h"},
{tdependent_param = "templates/dependent_param.h"},
{tdependent_using = "templates/dependent_using.h"},
{tdependent_using2 = "templates/dependent_using2.h"},
Expand Down
10 changes: 10 additions & 0 deletions tests/cpp/rpytest/ft/include/templates/dependent_base.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#pragma once

// Template class that has a non-template base class in the same file

struct TDBase {
virtual ~TDBase() {}
};

template <typename T>
struct TDChild : TDBase {};

0 comments on commit e6449d0

Please sign in to comment.