Line data Source code
1 0 : \cond NEVER 2 : Distributed under the MIT License. 3 : See LICENSE.txt for details. 4 : \endcond 5 : # Protocols {#protocols} 6 : 7 : \tableofcontents 8 : 9 : # Overview of protocols {#protocols_overview} 10 : 11 : Protocols are a concept we use in SpECTRE to define metaprogramming interfaces. 12 : A variation of this concept is built into many languages, so this is a quote 13 : from the [Swift documentation](https://docs.swift.org/swift-book/LanguageGuide/Protocols.html): 14 : 15 : > A protocol defines a blueprint of methods, properties, and other requirements 16 : > that suit a particular task or piece of functionality. The protocol can then 17 : > be adopted by a class, structure, or enumeration to provide an actual 18 : > implementation of those requirements. Any type that satisfies the requirements 19 : > of a protocol is said to conform to that protocol. 20 : 21 : You should define a protocol when you need a template parameter to conform to an 22 : interface. Here is an example of a protocol that is adapted from the [Swift 23 : documentation](https://docs.swift.org/swift-book/LanguageGuide/Protocols.html): 24 : 25 : \snippet Utilities/Test_ProtocolHelpers.cpp named_protocol 26 : 27 : The protocol defines an interface that any type that adopts it must implement. 28 : For example, the following class conforms to the protocol we just defined: 29 : 30 : \snippet Utilities/Test_ProtocolHelpers.cpp named_conformance 31 : 32 : The class indicates it conforms to the protocol by (publicly) inheriting from 33 : `tt::ConformsTo<TheProtocol>`. 34 : 35 : Once you have defined a protocol, you can check if a class conforms to it using 36 : the `tt::assert_conforms_to` or `tt::conforms_to` metafunctions: 37 : 38 : \snippet Utilities/Test_ProtocolHelpers.cpp conforms_to 39 : 40 : Note that checking for protocol conformance is cheap, so you may freely use 41 : protocol conformance checks in your code. 42 : 43 : This is how you can write code that relies on the interface defined by the 44 : protocol: 45 : 46 : \snippet Utilities/Test_ProtocolHelpers.cpp using_named_protocol 47 : 48 : Checking for protocol conformance here makes it clear that we are expecting 49 : a template parameter that exposes the particular interface we have defined in 50 : the protocol. Therefore, the author of the protocol and of the code that uses it 51 : has explicitly defined (and documented!) the interface they expect. And the 52 : developer who consumes the protocol by writing classes that conform to it knows 53 : exactly what needs to be implemented. 54 : 55 : Note that the `tt::conforms_to` metafunction is SFINAE-friendly, so you can use 56 : it like this: 57 : 58 : \snippet Utilities/Test_ProtocolHelpers.cpp protocol_sfinae 59 : 60 : The `tt::conforms_to` metafunction only checks if the class _indicates_ it 61 : conforms to the protocol. Where SFINAE-friendliness is not necessary prefer the 62 : `tt::assert_conforms_to` metafunction that triggers static asserts with 63 : diagnostic messages to understand why the class does not conform to the 64 : protocol. 65 : 66 : We typically define protocols in a file named `Protocols.hpp` and within a 67 : `protocols` namespace, similar to how we write \ref DataBoxTagsGroup "tags" in a 68 : `Tags.hpp` file and within a `Tags` namespace. The file should be placed in the 69 : directory associated with the code that depends on classes conforming to the 70 : protocols. For example, the protocol `Named` in the example above would be 71 : placed in directory that also has the `greet` function. 72 : 73 : # Protocol users: Conforming to a protocol {#protocols_conforming} 74 : 75 : To indicate a class conforms to a protocol it (publicly) inherits from 76 : `tt::ConformsTo<TheProtocol>`. The class must fulfill all requirements defined 77 : by the protocol. The requirements are listed in the protocol's documentation. 78 : 79 : Any class that indicates it conforms to a protocol must have a unit test to 80 : check that it actually does. You can use the `tt::assert_conforms_to` 81 : metafunction for the test: 82 : 83 : \snippet Utilities/Test_ProtocolHelpers.cpp test_protocol_conformance 84 : 85 : # Protocol authors: Writing a protocol {#protocols_author} 86 : 87 : To author a new protocol you implement a class that provides a `test` 88 : metafunction and detailed documentation. The `test` metafunction takes a single 89 : template parameter (typically named `ConformingType`) and checks that it 90 : conforms to the requirements laid out in the protocol's documentation. Its 91 : purpose is to provide diagnostic messages as compiler errors to understand why a 92 : type fails to conform to the protocol. You can use `static_assert`s or trigger 93 : standard compiler errors where appropriate. See the protocols defined above for 94 : examples. 95 : 96 : Occasionally, you might be tempted to add template parameters to a protocol. In 97 : those situations, add requirements to the protocol instead and retrieve the 98 : parameters from the conforming class. The reason for this guideline is that 99 : conforming classes will always inherit from `tt::ConformsTo<Protocol>`. 100 : Therefore, any template parameters of the protocol must also be template 101 : parameters of their conforming classes, which means the protocol can just 102 : require and retrieve them. For example, we could be tempted to follow this 103 : antipattern: 104 : 105 : \snippet Utilities/Test_ProtocolHelpers.cpp named_antipattern 106 : 107 : However, instead of adding template parameters to the protocol we should add a 108 : requirement to it: 109 : 110 : \snippet Utilities/Test_ProtocolHelpers.cpp named_with_type 111 : 112 : Classes would need to specify the template parameters for any 113 : protocols they conform to anyway, if the protocols had any. So they might as 114 : well expose them: 115 : 116 : \snippet Utilities/Test_ProtocolHelpers.cpp person_with_name_type 117 : 118 : This pattern also allows us to check for protocol conformance first and then add 119 : further checks about the types if we wanted to: 120 : 121 : \snippet Utilities/Test_ProtocolHelpers.cpp example_check_name_type 122 : 123 : # Protocol authors: Testing a protocol {#protocols_testing} 124 : 125 : Protocol authors should provide a unit test for their protocol that includes an 126 : example implementation of a class that conforms to it. The protocol author 127 : should add this example to the documentation of the protocol through a Doxygen 128 : snippet. This gives users a convenient way to see how the author intends their 129 : interface to be implemented. 130 : 131 : # Protocols and C++20 "Constraints and concepts" {#protocols_and_constraints} 132 : 133 : A feature related to protocols is in C++20 and goes under the name of 134 : [constraints and concepts](https://en.cppreference.com/w/cpp/language/constraints). 135 : Every protocol defines a _concept_, but it defers checking its requirements to 136 : the unit tests to save compile time. In other words, protocols provide a way to 137 : _indicate_ that a class fulfills a set of requirements, whereas C++20 138 : constraints provide a way to _check_ that a class fulfills a set of 139 : requirements. Therefore, the two features complement each other. Once C++20 140 : becomes available in SpECTRE we can either gradually convert our protocols to 141 : concepts and use them as constraints directly if we find the impact on compile 142 : time negligible, or we can add a concept that checks protocol conformance the 143 : same way that `tt::conforms_to_v` currently does (i.e. by checking inheritance).