C++¶
You defined a data model in DSM and ran Kibo over it. For C++, what you get back is
not a wrapper library — it is native C++ value types (structs, enum classes,
STL containers) plus the generated machinery that links them to the Viper C++
runtime. You write ordinary, value-semantic C++; the generated code crosses to the
dynamic runtime for you.
This page is that surface, end to end. For why the generated code exists (the adapter/Dual Reality pattern), see kibo-template-viper; for the persistence model, see Commit. The runtime it links against is Viper C++ (commercial).
Note
In the examples, App stands for your project name (the -n passed to Kibo) and
Tuto is the DSM namespace.
Anatomy of the generated C++¶
C++ is generated per feature, not as one importable package: each Kibo run emits a header/implementation pair, and you link the ones you need into your application.
<App>_Data.hpp / .cpp ← the types: struct / enum class / key, in namespace Tuto
<App>_Attachments.hpp / .cpp ← the verbs: get / set / enumerate, in namespace App::Tuto::Attachments
<App>_ValueEncoder.hpp / .cpp ← static C++ → dynamic Viper::Value
<App>_ValueDecoder.hpp / .cpp ← dynamic Viper::Value → static C++
<App>_ValueType.hpp / .cpp ← Viper type handles (plumbing)
Two namespaces divide the work: your value types land in the DSM namespace
(Tuto::Login, Tuto::UserKey), the machinery in your project namespace
(App::Tuto::Attachments::…, App::ValueEncoder, App::ValueDecoder). Every file
carries a “Do not edit by hand.” header — it is a build artifact, regenerated on
every model change.
Unlike Python and Node, C++ has a lexical namespace, so the DSM namespace becomes
a real namespace Tuto, not a Tuto_ symbol prefix.
Two representations, one bridge¶
This is where C++ differs most from the Python and Node SDKs. There, a proxy object
holds the runtime value (vpr_value / vprValue) — one value seen through a typed
view. In C++ there are two genuinely separate representations:
Developer Reality — native C++ value types:
Tuto::Login,Tuto::Status,std::set<Tuto::UserKey>,std::optional<Tuto::Login>. Value semantics, RAII, operators, zero wrapper overhead.Runtime Reality —
Viper::Value(dynamic,shared_ptr, metadata-driven), what the engine stores and manipulates.
The bridge between them is the generated ValueEncoder / ValueDecoder — the
Dual Reality made concrete:
std::shared_ptr<Viper::ValueStructure> v = App::ValueEncoder::encode(login); // static → dynamic
Tuto::Login back = App::ValueDecoder::decode_Tuto_Login(v); // dynamic → static
encode is overloaded on its argument (encode(Tuto::Login const&),
encode(std::optional<Tuto::Login> const&), encode(bool), …); decode is a
named function per type (decode_Tuto_Login, decode_bool, …), because C++
cannot overload on return type.
You rarely call them yourself: the attachment and database verbs take and return
native types and cross the bridge internally. You reach for encode / decode
explicitly only when you need a raw Viper::Value — serialization, RPC, or dynamic
interop. So there is no “don’t reach for vpr_value” rule here; the discipline is
simply to know which side of the bridge you are on and to cross it deliberately.
Living in the value types¶
Keys — concepts and clubs¶
A concept or club becomes a value class. A key is an identity, not data:
auto user = Tuto::UserKey::create(); // a fresh identity
user.instanceId(); // its UUId
user.description(); // "<uuid>:Tuto::UserKey"
user == other; // value comparison (operator==)
Keys are hashable and ordered (hash(), operator<), so they drop straight into
std::set<Tuto::UserKey> or std::map<Tuto::UserKey, …>.
Structs — plain public members¶
A struct becomes a value class with public data members and value constructors:
Tuto::Login login;
login.nickname = "alice";
login.password = "s3cret";
// or, all at once:
Tuto::Login other{"bob", "hunter2"};
login == other; // value equality, generated
Enums — enum class¶
Tuto::Account account{Tuto::Status::active};
account.state = Tuto::Status::pending;
Containers — the STL, directly¶
vector / set / map / optional map to their STL counterparts —
std::vector, std::set, std::map, std::optional — parameterized by the value
type. There is no proxy collection to learn: you use the STL type the generated API
hands you.
std::set<Tuto::UserKey> keys = /* … from an attachment … */;
for (Tuto::UserKey const & k : keys) {
// …
}
Mutating: calling an attachment verb¶
Data hangs off a key as attachments. Each attachment generates a namespace of
verbs — set, per-field set<Field>, get, keys, diff — taking native types
and a state drawn from a CommitDatabase (see Commit).
Write, then commit¶
using namespace App::Tuto::Attachments; // brings User_Login, User_Account, … into scope
auto db = CommitDatabase::open("model.cdb");
auto user = Tuto::UserKey::create();
Tuto::Login login{"alice", "s3cret"};
auto state = CommitMutableState::make(CommitStateBuilder::initialState(db));
User_Login::set(state->attachmentMutating(), user, login);
User_Login::setNickname(state->attachmentMutating(), user, "alice2"); // fine-grained
db->commitMutations("Register alice", state);
The verbs take native Tuto::Login and std::string — the encode step is internal.
Read¶
auto s = CommitMutableState::make(
CommitStateBuilder::state(db, db->lastCommitId().value()));
auto getting = s->attachmentGetting();
std::set<Tuto::UserKey> users = User_Login::keys(getting);
if (std::optional<Tuto::Login> maybe = User_Login::get(getting, user)) {
std::cout << maybe->nickname << '\n'; // "alice2"
}
get returns a std::optional<Tuto::Login> — a native value, already decoded.
The mental model is uniform: a verb is a free function in the attachment’s
namespace, taking a getting/mutating handle, a key, and (for setters) a native
value. A DatabaseAttachments feature offers the same verbs against a plain,
non-commit Viper::Database.
Reading a repro¶
Because every generated name is mechanical, an error written in generated symbols maps straight back to your model:
Tuto::Login→ typeLogininnamespace Tuto→ theDataheader, and the DSM linestruct Login { … }.App::Tuto::Attachments::User_Login::set→ projectApp, namespaceTuto, attachmentloginon key conceptUser, verbset→ the DSM lineattachment<User, Login> login;.App::ValueDecoder::decode_Tuto_Login→ the decoder that turns a runtimeViper::ValueStructureback into aTuto::Login.
From there:
The generated file says “Do not edit by hand.” The defect is upstream — in the DSM model, or in the template — never a patch to the output.
Regenerate after any model change (per feature,
kibo -c cpp -t cpp/<Feature>); the generated code is a build artifact and a stale copy will lie about the model.
That legibility is the payoff of the generated layer: the names you debug are the names you declared.