To write a new expression function, you can start with an existing function that does something similar, or write one from scratch. You need to:

The following is a code example for reference.

Write SeExprFunctionName.h

#pragma once
#include <SeExpr2/ExprFunc.h>
#include <SeExpr2/ExprNode.h>

class ExprFunctionNameSimple: public SeExpr2::ExprFuncSimple
{
public:
    ExprFunctionNameSimple() : SeExpr2::ExprFuncSimple(true) {} // Thread Safe
    virtual ~ExprFunctionNameSimple() {}

    SeExpr2::ExprType prep(SeExpr2::ExprFuncNode* node, bool scalarWanted, SeExpr2::ExprVarEnvBuilder& envBuilder) const;
    SeExpr2::ExprFuncNode::Data* evalConstant(const ExprFuncNode* node, ArgHandle args) const;
    void eval(ArgHandle args);
};

Write SeExprFunctionName.cpp

You may need to define a Data struct if you need to store any constant data. See evalConstant() method below.

<typename DATA_TYPE>
struct Data:public DATA_TYPE
{
}

You will need a prep() method to check number of arguments and their types.

SeExpr2::ExprType ExprFunctionNameSimple::prep(SeExpr2::ExprFuncNode* node,bool scalarWanted,SeExpr2::ExprVarEnv& env) const
{
    int nargs=node->numChildren();
    // check if number of args is correction
    // if not, return SeExpr2::ExprType().Error();

    bool valid=true;
   // check each arg for valid type
    valid &= node->checkArg(0,SeExpr2::ExprType().ArgType(),env);
    valid &= node->checkArg(1,SeExpr2::ExprType().ArgType(),env);

    // Return appropriate type or error, eg, SeExpr2::ExprType().FP(3).Varying()
    return valid ? SeExpr2::ExprType().FuncType() : SeExpr2::ExprType().Error();
}

You will also need an evalConstant() method to evaluate the arguments with expected constant value.

SeExpr2::ExprFuncNode::Data* ExprFuncNameSimple::evalConstant(ArgHandle args) const
{
    Data<SeExpr2::ExprFuncNode::Data>* data=new Data<SeExpr2::ExprFuncNode::Data>;

    // store any constant arg values in data

    return data;
}

If your function has no constant args, you can return an empty data struct.

SeExpr2::ExprFuncNode::Data*ExprFuncNameSimple::evalConstant(ArgHandle args) const
{
    return new Data<SeExpr2::ExprFuncNode::Data>();
}

You will also need an eval() method to evaluate the rest of the arguments.

void ExprFuncNameSimple::eval(ArgHandle args)
{
    SeExpr2::Vec3d result;
    // If you need access to constant data.
    Data<SeExpr2::ExprFuncNode::Data>& data=*static_cast<Data<SeExpr2::ExprFuncNode::Data>*>(args.data);

    // Calculate result of function using in args and data struct.
    // For example, to access the 0th input argument that is a vector value:
    result[0] = argsHandle.inFp<3>(0)[0];
    result[1] = argsHandle.inFp<3>{0}[1];
    result[2] = argsHandle.inFp<3>(0)[2];

    // Store result in ArgHandle output
    double* out=&args.outFp;
    for(int i=0;i<3;i++) out[i]=result[i];
}

Finally, you'll need to register your function, where funcName is the name used in expressions, nArgsMin is number of minimum required args, nArgsMax is number of maximum args allowed. Make sure you provide inline documentation about the function args and what the function does.

static int registered=registerDelegate2([](SeExpr2::ExprFunc::Define3 define){
    static ExprFuncNameSimple funcName(true);
    define("funcName", SeExpr2::ExprFunc(funcName,nArgsMin,nArgsMax), "Vec3d funcName( arg1, arg2, ...) some short description here about what the function does");
});