Type mappings for C++ native functions in SPL
When you declare C++ functions in function model files, you must write the declaration with SPL syntax that maps to C++.
The C++ native functions that are declared in the XML function model file are implemented in C++. The mapping between the SPL function signatures and the C++ function declarations and implementations is not necessarily one-to-one. In the general case, it might be an n-to-m mapping. This difference in mapping is a consequence of the differences between the SPL generics and C++ templates. However, in most common cases a given native function is implemented with a single C++ function. To map between SPL and C++, consider issues like making sure that SPL and C++ function parameters and return types are correctly matched.
An important rule to keep in mind is that mutable SPL types map
to reference versions of their C++ counterparts, and non-mutable SPL
types map to constant reference versions. For non-mutable primitive
types, the reference can be omitted in the C++ mapping. The following
table shows some example mappings between function signatures for
SPL and C++:
| // SPL function signatures | // C++ function declarations |
|---|---|
void foo(float64) |
void foo(float64) // or `float64 const &' |
void foo(mutable float64) |
void foo(float64 &) |
void foo(blob) |
void foo(blob const &) |
void bar(list<int32>) |
void bar(list<int32> const
&) |
void bar(mutable list<int32>)
|
void bar(list<int32> &) |
Native functions whose signatures involve SPL generics can often be implemented using C++
templates. As an example, consider the
size function in the following
example, which operates on any SPL collection type, including bounded
collections.<collection T> uint32 size(T vals) // SPL
| // C++ functions declarations | // C++ templates |
|---|---|
template <class T> |
template <class T, int32 N> |
uint32 size(list<T> const & vals) |
uint32 size(blist<T, N> const & vals) |
{ return vals.size(); } |
{ return vals.size(); } |
template <class K, class V> |
template <class K, class V, int32 N> |
uint32 size(map<K, V> const & vals)
|
uint32 size(bmap<K, V, N> const & vals) |
{ return vals.size(); } |
{ return vals.size(); } |
template <class T> |
template <class T, int32 N> |
uint32 size(set<T> const & vals) |
uint32 size(bset<T, N> const & vals) |
{ return vals.size(); } |
{ return vals.size(); } |
The most natural way to implement the
size function is to use a different C++
template function for each collection type in SPL, as shown. Alternatively, you can use
a single C++ template function to implement all, taking advantage of the fact that C++
implementations of all collection types provide the size() method. The
following example shows that
code:// Alternative using a single C++ template
template <class T> uint32 size(T const & vals) { return vals.size(); }
This implementation covers more than what the SPL signature of the
size function requires. However, from the perspective of SPL
applications, it is not a problem because the compiler performs type checks that are
based on the SPL signature. Nevertheless, this style of implementation is discouraged
because it accepts unintended instantiations in the C++ code.Another alternative implementation is to use the reflective type system. The result is in smaller
amount of code and is cleaner compared to the original implementation. However, it is
slightly more expensive in terms of the runtime
cost.
// Alternative implementation using reflective types
uint32 size(const List & vals) { return vals.getSize(); }
uint32 size(const Set & vals) { return vals.getSize(); }
uint32 size(const Map & vals) { return vals.getSize(); }
Finally, a single non-templated C++ function can also be created for handling all cases, by using
the reflective type system and the value handle support. It involves more runtime checks
and is expensive. Furthermore, similar to the single C++ template function
implementation, this approach also results in a C++ function that accepts calls that are
not allowed by the SPL signature. Even though such calls can be made only in C++ code
and not in SPL code because of the type checks that are performed by the SPL compiler.
The following is an implementation sketch for this
approach:
// Alternative implementation using value handle
uint32 size(ConstValueHandle value) {
Meta::Type type = val.getMetaType();
switch(type) {
case Meta::Type::List:
List const & lst = value;
return lst.getSize();
...
}
}
Tuples and enums in SPL map to C++ types that are generated by the SPL
compiler. As a result, C++ functions that involve these types are often implemented
using the reflection APIs. Reflective types such as Tuple and
Enum cannot be used as return types.The following code is an example native function that involves tuples, namely the
assignFrom function, which assigns attributes with matching names
and types from one tuple to
another.<tuple T1, tuple T2> void assignFrom (mutable T1 lhs, T2 rhs) // SPL void assignFrom(Tuple & lhs, Tuple const & rhs) //C++ <class T1, class T2> void assignFrom(T1 & lhs, T2 const & rhs) //C++ generic alternative
Another example is the
time function, which operates on a particular type of a
tuple, rather than on any valid
tuple.void time (timestamp time, mutable
tuple<int32 sec, int32 min, int32 hour, int32 mday,
int32 mon, int32 year, int32 wday, int32 yday,
int32 isdst, int32 gmtoff, rstring zone> result) //SPL
void time (timestamp const & time, Tuple & result) // C++
Enums are handled similarly. The following code is an
example:
void mylog(enum {error, info, debug, trace} level, ustring message) //SPL
void mylog(Enum const & level, ustring const & message) //C++