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