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++