Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@ cmake-build-debug

# VSCode settings
.vscode

# Autogenerated documentation
/docs/source/developer/cmakepp_lang/
41 changes: 41 additions & 0 deletions docs/source/features/classes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -455,3 +455,44 @@ or using ``ParentClass`` with a ``ChildClass`` instance:
If a call is made to the ``my_virtual_fxn`` function for an instance of
``ParentClass``, CMakePP will throw an error indicating that this function
is virtual and must be overridden in a derived class.


Extension Functions and Attributes
==================================
CMakePP classes support defining member functions and attributes
outside of the ``cpp_class``/``cpp_end_class`` fences. Such
definitions are called "extension functions" or "extension attributes."
Other languages such as Kotlin and C# have similar extension features.

What makes extension functions and attributes special is that
they can be defined by downstream users and have zero behavioral
differences from the predefined methods and attributes. Adding
an extension function or attribute changes the shape of the class
itself, and all existing instances of the class automatically
gain access to the extension. For extension attributes,
any existing instances have the default value for the attribute.

Constructors are also eligible for extension, and all extension constructors
and methods can overload existing methods or constructors.
Overload selection is done at time of method invocation, so
adding an extension can change which overload is selected
in the future.

Defining an Extension
---------------------
Defining an extension method, constructor, or attribute
is done exactly the same as defining a normal
method, constructor, or attribute, with one minor difference:
there is no surrounding :code:`cpp_class`/:code:`cpp_end_class` fence.
The extension definition *must* be in a scope where the class
being extended is visible and defined, so one cannot
make an extension to a class before the class itself has been
defined. This does not mean the extension definition has
to be in the same file; so long as the class is visible extensions
can be defined.

Example
-------
.. literalinclude:: /../../tests/docs/source/features/classes/define_extension_function.cmake
:lines: 6-24
:dedent: 4
40 changes: 40 additions & 0 deletions tests/class/class.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,17 @@ function(${_test_attr})
ct_assert_equal(res 3)
endfunction()

ct_add_section(NAME "_attr_set_single_val_extension")
function(${_attr_set_single_val_extension})
_attr_set_setup()
cpp_attr(MyClass c 4)
# Set attribute as single value
MyClass(SET "${my_instance}" c 3)
# Get the value and ensure it was set correctly
MyClass(GET "${my_instance}" res c)
ct_assert_equal(res 3)
endfunction()

ct_add_section(NAME "_attr_set_list")
function(${_attr_set_list})
_attr_set_setup()
Expand All @@ -330,6 +341,18 @@ function(${_test_attr})
ct_assert_equal(res "1;2;3")
endfunction()

ct_add_section(NAME "_attr_set_list_extension")
function(${_attr_set_list_extension})
_attr_set_setup()
cpp_attr(MyClass c "4;5")

# Set attribute as a list of values
MyClass(SET "${my_instance}" c 1 2 3)
# Get the value and ensure it was set correctly
MyClass(GET "${my_instance}" res c)
ct_assert_equal(res "1;2;3")
endfunction()

endfunction()
endfunction()

Expand Down Expand Up @@ -423,6 +446,23 @@ function(${_test_constructor})
ct_assert_equal(res 3)
endfunction()

ct_add_section(NAME "_multi_construct_third_extension_two_int_params")
function(${_multi_construct_third_extension_two_int_params})
_constructor_multiple_customs_setup()

cpp_constructor(CTOR MyClass int int)
function("${CTOR}" self a b)
# Change value of my_attr
math(EXPR new_val "${a} + ${b}")
MyClass(SET "${self}" my_attr "${new_val}")
endfunction()

# Create instance and test that the 1st CTOR was called
MyClass(CTOR my_instance 3 7)
MyClass(GET "${my_instance}" res my_attr)
ct_assert_equal(res 10)
endfunction()

endfunction()

ct_add_section(NAME "_construct_kwargs")
Expand Down
26 changes: 26 additions & 0 deletions tests/docs/source/features/classes/define_extension_function.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
include(cmake_test/cmake_test)

ct_add_test(NAME "define_extension_function")
function("${define_extension_function}")

cpp_class(MyClass)

# Class with no members

cpp_end_class()

# Adding an extension function
cpp_member(my_fxn MyClass type_a type_b)
function("${my_fxn}" self param_a param_b)

# The body of the function

# ${self} can be used to access the instance of MyClass
# the function is being called with

# ${param_a} and ${param_b} can be used to access the
# values of the parameters passed into the function

endfunction()

endfunction()