API Reference

API Reference


Try it out

Try it out in Candid UI

The API Reference example code can be found in Github repo icpp-demos.

CandidTypes

Candid / C++ conversion tables

CandidType Candid C++ Example
CandidTypeBool bool bool example
CandidTypeEmpty empty - -
CandidTypeFloat32 float32 float example
CandidTypeFloat64 float64 double example
CandidTypeInt8 int8 int8_t example
CandidTypeInt16 int16 int16_t example
CandidTypeInt32 int32 int32_t example
CandidTypeInt64 int64 int64_t example
CandidTypeInt int __int128_t example
CandidTypeNat8 nat8 uint8_t example
CandidTypeNat16 nat16 uint16_t example
CandidTypeNat32 nat32 uint32_t example
CandidTypeNat64 nat64 uint64_t example
CandidTypeNat nat __uint128_t example
CandidTypeNull null - -
CandidTypeOpt opt std::optional example
CandidTypePrincipal principal std::string example
CandidTypeRecord record custom example
CandidTypeText text std::string example
CandidTypeVec vec std::vector example
CandidTypeVariant variant custom example

CandidTypeBool

A class to convert between Candid bool and C++ bool.

Declaration:

// candid.h
class CandidTypeBool {
public:
  CandidTypeBool(bool *v);
  CandidTypeBool(const bool v); 
}

Example:

/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_bool.cpp

$ dfx canister call --type idl --output idl demo demo_candid_type_bool '(true)'
(true)
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_bool received value '1'

$ dfx canister call --type idl --output idl demo demo_candid_type_bools '(true, false)'
(true, false)
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_bools received values '1' & '0'

*/
#include "demo_candid_type_bool.h"

#include <string>

#include "ic_api.h"

void demo_candid_type_bool() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  bool in{false};
  ic_api.from_wire(CandidTypeBool{&in});
  IC_API::debug_print("Method " + std::string(__func__) + " received value '" +
                      std::to_string(in) + "'");
  ic_api.to_wire(CandidTypeBool{in});
}

void demo_candid_type_bools() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  bool in1{false};
  bool in2{false};
  CandidArgs args_in;
  args_in.append(CandidTypeBool(&in1));
  args_in.append(CandidTypeBool(&in2));
  ic_api.from_wire(args_in);

  IC_API::debug_print("Method " + std::string(__func__) + " received values '" +
                      std::to_string(in1) + "' & '" + std::to_string(in2) +
                      "'");

  CandidArgs args_out;
  args_out.append(CandidTypeBool(in1));
  args_out.append(CandidTypeBool(in2));
  ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_bool.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_bool()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_bool");
void demo_candid_type_bools()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_bools");
// file: src/demo.did
service : {
    "demo_candid_type_bool" : (bool) -> (bool) query;
    "demo_candid_type_bools" : (bool, bool) -> (bool, bool) query; 
};

CandidTypeFloat32

A class to convert between Candid float32 and C++ float.

Declaration:

// candid.h
class CandidTypeFloat32 {
public:
  CandidTypeFloat32(float *v);
  CandidTypeFloat32(const float v); 
}

Example:

/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_float32.cpp

$ dfx canister call --type idl --output idl demo demo_candid_type_float32 '(0.1 : float32)'
(0.1 : float32)
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_float32 received value '0.100000'

$ dfx canister call --type idl --output idl demo demo_candid_type_float32s '(0.1 : float32, -1.2 : float32)'
(0.1 : float32, -1.2 : float32)
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_float32s received values '0.100000' & '-1.200000'

*/
#include "demo_candid_type_float32.h"

#include <string>

#include "ic_api.h"

void demo_candid_type_float32() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  float in{0.0};
  ic_api.from_wire(CandidTypeFloat32{&in});
  IC_API::debug_print("Method " + std::string(__func__) + " received value '" +
                      std::to_string(in) + "'");
  ic_api.to_wire(CandidTypeFloat32{in});
}

void demo_candid_type_float32s() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  float in1{0.0};
  float in2{0.0};
  CandidArgs args_in;
  args_in.append(CandidTypeFloat32(&in1));
  args_in.append(CandidTypeFloat32(&in2));
  ic_api.from_wire(args_in);

  IC_API::debug_print("Method " + std::string(__func__) + " received values '" +
                      std::to_string(in1) + "' & '" + std::to_string(in2) +
                      "'");

  CandidArgs args_out;
  args_out.append(CandidTypeFloat32(in1));
  args_out.append(CandidTypeFloat32(in2));
  ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_float32.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_float32()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_float32");
void demo_candid_type_float32s()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_float32s");
// file: src/demo.did
service : {
    "demo_candid_type_float32" : (float32) -> (float32) query;
    "demo_candid_type_float32s" : (float32, float32) -> (float32, float32) query; 
};

CandidTypeFloat64

A class to convert between Candid float64 and C++ double.

Declaration:

// candid.h
class CandidTypeFloat64 {
public:
  CandidTypeFloat64(double *v);
  CandidTypeFloat64(const double v); 
}

Example:

/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_float64.cpp

$ dfx canister call --type idl --output idl demo demo_candid_type_float64 '(0.1 : float64)'
(0.1 : float64)
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_float64 received value '0.100000'

$ dfx canister call --type idl --output idl demo demo_candid_type_float64s '(0.1 : float64, -1.2 : float64)'
(0.1 : float64, -1.2 : float64)
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_float64s received values '0.100000' & '-1.200000'

*/
#include "demo_candid_type_float64.h"

#include <string>

#include "ic_api.h"

void demo_candid_type_float64() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  double in{0.0};
  ic_api.from_wire(CandidTypeFloat64{&in});
  IC_API::debug_print("Method " + std::string(__func__) + " received value '" +
                      std::to_string(in) + "'");
  ic_api.to_wire(CandidTypeFloat64{in});
}

void demo_candid_type_float64s() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  double in1{0.0};
  double in2{0.0};
  CandidArgs args_in;
  args_in.append(CandidTypeFloat64(&in1));
  args_in.append(CandidTypeFloat64(&in2));
  ic_api.from_wire(args_in);

  IC_API::debug_print("Method " + std::string(__func__) + " received values '" +
                      std::to_string(in1) + "' & '" + std::to_string(in2) +
                      "'");

  CandidArgs args_out;
  args_out.append(CandidTypeFloat64(in1));
  args_out.append(CandidTypeFloat64(in2));
  ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_float64.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_float64()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_float64");
void demo_candid_type_float64s()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_float64s");
// file: src/demo.did
service : {
    "demo_candid_type_float64" : (float64) -> (float64) query;
    "demo_candid_type_float64s" : (float64, float64) -> (float64, float64) query; 
};

CandidTypeInt8

A class to convert between Candid int8 and C++ int8_t.

Declaration:

// candid.h
class CandidTypeInt8 {
public:
  CandidTypeInt8(int8_t *v);
  CandidTypeInt8(const int8_t v); 
}

Example:

/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_int8.cpp

$ dfx canister call --type idl --output idl demo demo_candid_type_int8 '(101 : int8)'
(101 : int8)
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_int8 received value '101'

$ dfx canister call --type idl --output idl demo demo_candid_type_int8s '(101 : int8, -102 : int8)'
(101 : int8, -102 : int8)
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_int8s received values '101' & '-102'

*/
#include "demo_candid_type_int8.h"

#include <string>

#include "ic_api.h"

void demo_candid_type_int8() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  int8_t in{0};
  ic_api.from_wire(CandidTypeInt8{&in});
  IC_API::debug_print("Method " + std::string(__func__) + " received value '" +
                      std::to_string(in) + "'");
  ic_api.to_wire(CandidTypeInt8{in});
}

void demo_candid_type_int8s() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  int8_t in1{0};
  int8_t in2{0};
  CandidArgs args_in;
  args_in.append(CandidTypeInt8(&in1));
  args_in.append(CandidTypeInt8(&in2));
  ic_api.from_wire(args_in);

  IC_API::debug_print("Method " + std::string(__func__) + " received values '" +
                      std::to_string(in1) + "' & '" + std::to_string(in2) +
                      "'");

  CandidArgs args_out;
  args_out.append(CandidTypeInt8(in1));
  args_out.append(CandidTypeInt8(in2));
  ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_int8.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_int8()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_int8");
void demo_candid_type_int8s()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_int8s");
// file: src/demo.did
service : {
    "demo_candid_type_int8" : (int8) -> (int8) query;
    "demo_candid_type_int8s" : (int8, int8) -> (int8, int8) query; 
};

CandidTypeInt16

A class to convert between Candid int16 and C++ int16_t.

Declaration:

// candid.h
class CandidTypeInt16 {
public:
  CandidTypeInt16(int16_t *v);
  CandidTypeInt16(const int16_t v); 
}

Example:

/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_int16.cpp

$ dfx canister call --type idl --output idl demo demo_candid_type_int16 '(101 : int16)'
(101 : int16)
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_int16 received value '101'

$ dfx canister call --type idl --output idl demo demo_candid_type_int16s '(101 : int16, -102 : int16)'
(101 : int16, -102 : int16)
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_int16s received values '101' & '-102'

*/
#include "demo_candid_type_int16.h"

#include <string>

#include "ic_api.h"

void demo_candid_type_int16() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  int16_t in{0};
  ic_api.from_wire(CandidTypeInt16{&in});
  IC_API::debug_print("Method " + std::string(__func__) + " received value '" +
                      std::to_string(in) + "'");
  ic_api.to_wire(CandidTypeInt16{in});
}

void demo_candid_type_int16s() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  int16_t in1{0};
  int16_t in2{0};
  CandidArgs args_in;
  args_in.append(CandidTypeInt16(&in1));
  args_in.append(CandidTypeInt16(&in2));
  ic_api.from_wire(args_in);

  IC_API::debug_print("Method " + std::string(__func__) + " received values '" +
                      std::to_string(in1) + "' & '" + std::to_string(in2) +
                      "'");

  CandidArgs args_out;
  args_out.append(CandidTypeInt16(in1));
  args_out.append(CandidTypeInt16(in2));
  ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_int16.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_int16()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_int16");
void demo_candid_type_int16s()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_int16s");
// file: src/demo.did
service : {
    "demo_candid_type_int16" : (int16) -> (int16) query;
    "demo_candid_type_int16s" : (int16, int16) -> (int16, int16) query; 
};

CandidTypeInt32

A class to convert between Candid int32 and C++ int32_t.

Declaration:

// candid.h
class CandidTypeInt32 {
public:
  CandidTypeInt32(int32_t *v);
  CandidTypeInt32(const int32_t v); 
}

Example:

/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_int32.cpp

$ dfx canister call --type idl --output idl demo demo_candid_type_int32 '(101 : int32)'
(101 : int32)
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_int32 received value '101'

$ dfx canister call --type idl --output idl demo demo_candid_type_int32s '(101 : int32, -102 : int32)'
(101 : int32, -102 : int32)
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_int32s received values '101' & '-102'

*/
#include "demo_candid_type_int32.h"

#include <string>

#include "ic_api.h"

void demo_candid_type_int32() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  int32_t in{0};
  ic_api.from_wire(CandidTypeInt32{&in});
  IC_API::debug_print("Method " + std::string(__func__) + " received value '" +
                      std::to_string(in) + "'");
  ic_api.to_wire(CandidTypeInt32{in});
}

void demo_candid_type_int32s() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  int32_t in1{0};
  int32_t in2{0};
  CandidArgs args_in;
  args_in.append(CandidTypeInt32(&in1));
  args_in.append(CandidTypeInt32(&in2));
  ic_api.from_wire(args_in);

  IC_API::debug_print("Method " + std::string(__func__) + " received values '" +
                      std::to_string(in1) + "' & '" + std::to_string(in2) +
                      "'");

  CandidArgs args_out;
  args_out.append(CandidTypeInt32(in1));
  args_out.append(CandidTypeInt32(in2));
  ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_int32.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_int32()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_int32");
void demo_candid_type_int32s()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_int32s");
// file: src/demo.did
service : {
    "demo_candid_type_int32" : (int32) -> (int32) query;
    "demo_candid_type_int32s" : (int32, int32) -> (int32, int32) query; 
};

CandidTypeInt64

A class to convert between Candid int64 and C++ int64_t.

Declaration:

// candid.h
class CandidTypeInt64 {
public:
  CandidTypeInt64(int64_t *v);
  CandidTypeInt64(const int64_t v); 
}

Example:

/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_int64.cpp

$ dfx canister call --type idl --output idl demo demo_candid_type_int64 '(101 : int64)'
(101 : int64)
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_int64 received value '101'

$ dfx canister call --type idl --output idl demo demo_candid_type_int64s '(101 : int64, -102 : int64)'
(101 : int64, -102 : int64)
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_int64s received values '101' & '-102'

*/
#include "demo_candid_type_int64.h"

#include <string>

#include "ic_api.h"

void demo_candid_type_int64() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  int64_t in{0};
  ic_api.from_wire(CandidTypeInt64{&in});
  IC_API::debug_print("Method " + std::string(__func__) + " received value '" +
                      std::to_string(in) + "'");
  ic_api.to_wire(CandidTypeInt64{in});
}

void demo_candid_type_int64s() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  int64_t in1{0};
  int64_t in2{0};
  CandidArgs args_in;
  args_in.append(CandidTypeInt64(&in1));
  args_in.append(CandidTypeInt64(&in2));
  ic_api.from_wire(args_in);

  IC_API::debug_print("Method " + std::string(__func__) + " received values '" +
                      std::to_string(in1) + "' & '" + std::to_string(in2) +
                      "'");

  CandidArgs args_out;
  args_out.append(CandidTypeInt64(in1));
  args_out.append(CandidTypeInt64(in2));
  ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_int64.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_int64()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_int64");
void demo_candid_type_int64s()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_int64s");
// file: src/demo.did
service : {
    "demo_candid_type_int64" : (int64) -> (int64) query;
    "demo_candid_type_int64s" : (int64, int64) -> (int64, int64) query; 
};

CandidTypeInt

A class to convert between Candid int and C++ __int128_t.

Declaration:

// candid.h
class CandidTypeInt {
public:
  CandidTypeInt(__int128_t *v);
  CandidTypeInt(const __int128_t &v); 
}

Example:

/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_int.cpp

$ dfx canister call --type idl --output idl demo demo_candid_type_int '(101 : int)'
(101 : int)
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_int received value '101'

$ dfx canister call --type idl --output idl demo demo_candid_type_ints '(101 : int, -102 : int)'
(101 : int, -102 : int)
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_ints received values '101' & '-102'

*/
#include "demo_candid_type_int.h"

#include <string>

#include "ic_api.h"

void demo_candid_type_int() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  __int128_t in{0};
  ic_api.from_wire(CandidTypeInt{&in});
  IC_API::debug_print("Method " + std::string(__func__) + " received value '" +
                      IC_API::to_string_128(in) + "'");
  ic_api.to_wire(CandidTypeInt{in});
}

void demo_candid_type_ints() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  __int128_t in1{0};
  __int128_t in2{0};
  CandidArgs args_in;
  args_in.append(CandidTypeInt(&in1));
  args_in.append(CandidTypeInt(&in2));
  ic_api.from_wire(args_in);

  IC_API::debug_print("Method " + std::string(__func__) + " received values '" +
                      IC_API::to_string_128(in1) + "' & '" +
                      IC_API::to_string_128(in2) + "'");

  CandidArgs args_out;
  args_out.append(CandidTypeInt(in1));
  args_out.append(CandidTypeInt(in2));
  ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_int.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_int()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_int");
void demo_candid_type_ints()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_ints");
// file: src/demo.did
service : {
    "demo_candid_type_int" : (int) -> (int) query;
    "demo_candid_type_ints" : (int, int) -> (int, int) query; 
};

CandidTypeNat8

A class to convert between Candid int8 and C++ uint8_t.

Declaration:

// candid.h
class CandidTypeNat8 {
public:
  CandidTypeNat8(uint8_t *v);
  CandidTypeNat8(const uint8_t v); 
}

Example:

/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_nat8.cpp

$ dfx canister call --type idl --output idl demo demo_candid_type_nat8 '(101 : nat8)'
(101 : nat8)
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_nat8 received value '101'

$ dfx canister call --type idl --output idl demo demo_candid_type_nat8s '(101 : nat8, 102 : nat8)'
(101 : nat8, 102 : nat8)
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_nat8s received values '101' & '102'

*/
#include "demo_candid_type_nat8.h"

#include <string>

#include "ic_api.h"

void demo_candid_type_nat8() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  uint8_t in{0};
  ic_api.from_wire(CandidTypeNat8{&in});
  IC_API::debug_print("Method " + std::string(__func__) + " received value '" +
                      std::to_string(in) + "'");
  ic_api.to_wire(CandidTypeNat8{in});
}

void demo_candid_type_nat8s() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  uint8_t in1{0};
  uint8_t in2{0};
  CandidArgs args_in;
  args_in.append(CandidTypeNat8(&in1));
  args_in.append(CandidTypeNat8(&in2));
  ic_api.from_wire(args_in);

  IC_API::debug_print("Method " + std::string(__func__) + " received values '" +
                      std::to_string(in1) + "' & '" + std::to_string(in2) +
                      "'");

  CandidArgs args_out;
  args_out.append(CandidTypeNat8(in1));
  args_out.append(CandidTypeNat8(in2));
  ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_nat8.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_nat8()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_nat8");
void demo_candid_type_nat8s()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_nat8s");
// file: src/demo.did
service : {
    "demo_candid_type_nat8" : (nat8) -> (nat8) query;
    "demo_candid_type_nat8s" : (nat8, nat8) -> (nat8, nat8) query; 
};

CandidTypeNat16

A class to convert between Candid int16 and C++ uint16_t.

Declaration:

// candid.h
class CandidTypeNat16 {
public:
  CandidTypeNat16(uint16_t *v);
  CandidTypeNat16(const uint16_t v); 
}

Example:

/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_nat16.cpp

$ dfx canister call --type idl --output idl demo demo_candid_type_nat16 '(101 : nat16)'
(101 : nat16)
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_nat16 received value '101'

$ dfx canister call --type idl --output idl demo demo_candid_type_nat16s '(101 : nat16, 102 : nat16)'
(101 : nat16, 102 : nat16)
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_nat16s received values '101' & '102'

*/
#include "demo_candid_type_nat16.h"

#include <string>

#include "ic_api.h"

void demo_candid_type_nat16() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  uint16_t in{0};
  ic_api.from_wire(CandidTypeNat16{&in});
  IC_API::debug_print("Method " + std::string(__func__) + " received value '" +
                      std::to_string(in) + "'");
  ic_api.to_wire(CandidTypeNat16{in});
}

void demo_candid_type_nat16s() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  uint16_t in1{0};
  uint16_t in2{0};
  CandidArgs args_in;
  args_in.append(CandidTypeNat16(&in1));
  args_in.append(CandidTypeNat16(&in2));
  ic_api.from_wire(args_in);

  IC_API::debug_print("Method " + std::string(__func__) + " received values '" +
                      std::to_string(in1) + "' & '" + std::to_string(in2) +
                      "'");

  CandidArgs args_out;
  args_out.append(CandidTypeNat16(in1));
  args_out.append(CandidTypeNat16(in2));
  ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_nat16.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_nat16()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_nat16");
void demo_candid_type_nat16s()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_nat16s");
// file: src/demo.did
service : {
    "demo_candid_type_nat16" : (nat16) -> (nat16) query;
    "demo_candid_type_nat16s" : (nat16, nat16) -> (nat16, nat16) query; 
};

CandidTypeNat32

A class to convert between Candid int32 and C++ uint32_t.

Declaration:

// candid.h
class CandidTypeNat32 {
public:
  CandidTypeNat32(uint32_t *v);
  CandidTypeNat32(const uint32_t v); 
}

Example:

/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_nat32.cpp

$ dfx canister call --type idl --output idl demo demo_candid_type_nat32 '(101 : nat32)'
(101 : nat32)
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_nat32 received value '101'

$ dfx canister call --type idl --output idl demo demo_candid_type_nat32s '(101 : nat32, 102 : nat32)'
(101 : nat32, 102 : nat32)
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_nat32s received values '101' & '102'

*/
#include "demo_candid_type_nat32.h"

#include <string>

#include "ic_api.h"

void demo_candid_type_nat32() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  uint32_t in{0};
  ic_api.from_wire(CandidTypeNat32{&in});
  IC_API::debug_print("Method " + std::string(__func__) + " received value '" +
                      std::to_string(in) + "'");
  ic_api.to_wire(CandidTypeNat32{in});
}

void demo_candid_type_nat32s() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  uint32_t in1{0};
  uint32_t in2{0};
  CandidArgs args_in;
  args_in.append(CandidTypeNat32(&in1));
  args_in.append(CandidTypeNat32(&in2));
  ic_api.from_wire(args_in);

  IC_API::debug_print("Method " + std::string(__func__) + " received values '" +
                      std::to_string(in1) + "' & '" + std::to_string(in2) +
                      "'");

  CandidArgs args_out;
  args_out.append(CandidTypeNat32(in1));
  args_out.append(CandidTypeNat32(in2));
  ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_nat32.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_nat32()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_nat32");
void demo_candid_type_nat32s()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_nat32s");
// file: src/demo.did
service : {
    "demo_candid_type_nat32" : (nat32) -> (nat32) query;
    "demo_candid_type_nat32s" : (nat32, nat32) -> (nat32, nat32) query; 
};

CandidTypeNat64

A class to convert between Candid int64 and C++ int64_t.

Declaration:

// candid.h
class CandidTypeNat64 {
public:
  CandidTypeNat64(uint64_t *v);
  CandidTypeNat64(const uint64_t v); 
}

Example:

/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_nat64.cpp

$ dfx canister call --type idl --output idl demo demo_candid_type_nat64 '(101 : nat64)'
(101 : nat64)
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_nat64 received value '101'

$ dfx canister call --type idl --output idl demo demo_candid_type_nat64s '(101 : nat64, 102 : nat64)'
(101 : nat64, 102 : nat64)
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_nat64s received values '101' & '102'

*/
#include "demo_candid_type_nat64.h"

#include <string>

#include "ic_api.h"

void demo_candid_type_nat64() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  uint64_t in{0};
  ic_api.from_wire(CandidTypeNat64{&in});
  IC_API::debug_print("Method " + std::string(__func__) + " received value '" +
                      std::to_string(in) + "'");
  ic_api.to_wire(CandidTypeNat64{in});
}

void demo_candid_type_nat64s() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  uint64_t in1{0};
  uint64_t in2{0};
  CandidArgs args_in;
  args_in.append(CandidTypeNat64(&in1));
  args_in.append(CandidTypeNat64(&in2));
  ic_api.from_wire(args_in);

  IC_API::debug_print("Method " + std::string(__func__) + " received values '" +
                      std::to_string(in1) + "' & '" + std::to_string(in2) +
                      "'");

  CandidArgs args_out;
  args_out.append(CandidTypeNat64(in1));
  args_out.append(CandidTypeNat64(in2));
  ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_nat64.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_nat64()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_nat64");
void demo_candid_type_nat64s()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_nat64s");
// file: src/demo.did
service : {
    "demo_candid_type_nat64" : (nat64) -> (nat64) query;
    "demo_candid_type_nat64s" : (nat64, nat64) -> (nat64, nat64) query; 
};

CandidTypeNat

A class to convert between Candid int and C++ __uint128_t.

Declaration:

// candid.h
class CandidTypeNat {
public:
  CandidTypeNat(__uint128_t *v);
  CandidTypeNat(const __uint128_t v); 
}

Example:

/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_nat.cpp

$ dfx canister call --type idl --output idl demo demo_candid_type_nat '(101 : nat)'
(101 : nat)
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_nat received value '101'

$ dfx canister call --type idl --output idl demo demo_candid_type_nats '(101 : nat, 102 : nat)'
(101 : nat, 102 : nat)
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_nats received values '101' & '102'

*/
#include "demo_candid_type_nat.h"

#include <string>

#include "ic_api.h"

void demo_candid_type_nat() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  __uint128_t in{0};
  ic_api.from_wire(CandidTypeNat{&in});
  IC_API::debug_print("Method " + std::string(__func__) + " received value '" +
                      IC_API::to_string_128(in) + "'");
  ic_api.to_wire(CandidTypeNat{in});
}

void demo_candid_type_nats() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  __uint128_t in1{0};
  __uint128_t in2{0};
  CandidArgs args_in;
  args_in.append(CandidTypeNat(&in1));
  args_in.append(CandidTypeNat(&in2));
  ic_api.from_wire(args_in);

  IC_API::debug_print("Method " + std::string(__func__) + " received values '" +
                      IC_API::to_string_128(in1) + "' & '" +
                      IC_API::to_string_128(in2) + "'");

  CandidArgs args_out;
  args_out.append(CandidTypeNat(in1));
  args_out.append(CandidTypeNat(in2));
  ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_nat.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_nat()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_nat");
void demo_candid_type_nats()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_nats");
// file: src/demo.did
service : {
    "demo_candid_type_nat" : (nat) -> (nat) query;
    "demo_candid_type_nats" : (nat, nat) -> (nat, nat) query; 
};

CandidTypeOpt

A group of classes to convert between Candid opt candid-type and C++ std::optional<C++ type>.

CandidTypeOpt Candid C++ Example
CandidTypeOptBool opt bool std::optional<bool> example
not supported opt empty not supported -
CandidTypeOptFloat32 opt float32 std::optional<float> example
CandidTypeOptFloat64 opt float64 std::optional<double> example
CandidTypeOptInt8 opt int8 std::optional<int8_t> example
CandidTypeOptInt16 opt int16 std::optional<int16_t> example
CandidTypeOptInt32 opt int32 std::optional<int32_t> example
CandidTypeOptInt64 opt int64 std::optional<int64_t> example
CandidTypeOptInt opt int std::optional<__int128_t> example
CandidTypeOptNat8 opt nat8 std::optional<uint8_t> example
CandidTypeOptNat16 opt nat16 std::optional<uint16_t> example
CandidTypeOptNat32 opt nat32 std::optional<uint32_t> example
CandidTypeOptNat64 opt nat64 std::optional<uint64_t> example
CandidTypeOptNat opt nat std::optional<__uint128_t> example
not supported opt null not supported -
CandidTypeOptPrincipal opt principal std::optional<std::string> example
CandidTypeOptText opt text std::optional<std::string> example

Declaration:

// candid.h
class CandidTypeOptBool {
public:
  CandidTypeOptBool(std::optional<bool> *v);
  CandidTypeOptBool(const std::optional<bool> v); 
}

class CandidTypeOptFloat32 {
public:
  CandidTypeOptFloat32(std::optional<float> *v);
  CandidTypeOptFloat32(const std::optional<float> v); 
}

class CandidTypeOptFloat64 {
public:
  CandidTypeOptFloat64(std::optional<double> *v);
  CandidTypeOptFloat64(const std::optional<double> v); 
}

class CandidTypeOptInt8 {
public:
  CandidTypeOptInt8(std::optional<int8_t> *v);
  CandidTypeOptInt8(const std::optional<int8_t> v); 
}

class CandidTypeOptInt16 {
public:
  CandidTypeOptInt16(std::optional<int16_t> *v);
  CandidTypeOptInt16(const std::optional<int16_t> v); 
}

class CandidTypeOptInt32 {
public:
  CandidTypeOptInt32(std::optional<int32_t> *p_v);
  CandidTypeOptInt32(const std::optional<int32_t> v); 
}

class CandidTypeOptInt64 {
public:
  CandidTypeOptInt64(std::optional<int64_t> *v);
  CandidTypeOptInt64(const std::optional<int64_t> v); 
}

class CandidTypeOptInt {
public:
  CandidTypeOptInt(std::optional<__int128_t> *v);
  CandidTypeOptInt(const std::optional<__int128_t> v); 
}

class CandidTypeOptNat8 {
public:
  CandidTypeOptNat8(std::optional<uint8_t> *v);
  CandidTypeOptNat8(const std::optional<uint8_t> v); 
}

class CandidTypeOptNat16 {
public:
  CandidTypeOptNat16(std::optional<uint16_t> *v);
  CandidTypeOptNat16(const std::optional<uint16_t> v); 
}

class CandidTypeOptNat32 {
public:
  CandidTypeOptNat32(std::optional<uint32_t> *v);
  CandidTypeOptNat32(const std::optional<uint32_t> v); 
}

class CandidTypeOptNat64 {
public:
  CandidTypeOptNat64(std::optional<uint64_t> *v);
  CandidTypeOptNat64(const std::optional<uint64_t> v); 
}

class CandidTypeOptNat {
public:
  CandidTypeOptNat(std::optional<__uint128_t> *v);
  CandidTypeOptNat(const std::optional<__uint128_t> v); 
}

class CandidTypeOptPrincipal {
public:
  CandidTypeOptPrincipal(std::optional<std::string> *p_v);
  CandidTypeOptPrincipal(const std::optional<std::string> v); 
}

class CandidTypeOptText {
public:
  CandidTypeOptText(std::optional<std::string> *v);
  CandidTypeOptText(const std::optional<std::string> v); 
}

Example:

/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_opt.cpp

// ----------------------------------------------------------
// The (opt bool) has three possible values:
// - true
// - false
// - no value
// Let's try them all out:

$ dfx canister call --type idl --output idl demo demo_candid_type_opt '(opt (true : bool))'
(opt true)
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opt received bool value '1'

$ dfx canister call --type idl --output idl demo demo_candid_type_opt '(opt (false : bool))'
(opt false)
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opt received bool value '0'

$ dfx canister call --type idl --output idl demo demo_candid_type_opt '(null)'
(null)
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opt did not receive a bool value

// ----------------------------------------------------------
// The other method has as argument a list of all opt types, eg.

$ dfx canister call --type idl --output idl demo demo_candid_type_opts '(opt (true : bool), opt (0.1 : float32), opt (0.2 : float64), opt (-8 : int8), opt (-16 : int16), opt (-32 : int32), opt (-64 : int64), opt (-128 : int), opt (8 : nat8), opt (16 : nat16), opt (32 : nat32), opt (64 : nat64), opt (128 : nat), opt (principal "2ibo7-dia"), opt ("demo" : text))'
(opt true, opt (0.1 : float32), opt (0.2 : float64), opt (-8 : int8), opt (-16 : int16), opt (-32 : int32), opt (-64 : int64), opt (-128 : int), opt (8 : nat8), opt (16 : nat16), opt (32 : nat32), opt (64 : nat64), opt (128 : nat), opt principal "2ibo7-dia", opt "demo")
-> check the console of the local network. The canister will print:
  [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opts received bool value '1'
  [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opts received float32 value '0.100000'
  [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opts received float64 value '0.200000'
  [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opts received int8 value '-8'
  [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opts received int16 value '-16'
  [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opts received int32 value '-32'
  [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opts received int64 value '-64'
  [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opts received int value '-128'
  [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opts received nat8 value '8'
  [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opts received nat16 value '16'
  [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opts received nat32 value '32'
  [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opts received nat64 value '64'
  [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opts received nat value '128'
  [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opts received principal value '2ibo7-dia'
  [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opts received text value 'demo'

*/
#include "demo_candid_type_opt.h"

#include <string>

#include "ic_api.h"

void demo_candid_type_opt() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  std::optional<bool> in;
  ic_api.from_wire(CandidTypeOptBool{&in});

  if (in.has_value()) {
    IC_API::debug_print("Method " + std::string(__func__) +
                        " received bool value '" + std::to_string(in.value()) +
                        "'");
  } else {
    IC_API::debug_print("Method " + std::string(__func__) +
                        " did not receive a bool value");
  }

  ic_api.to_wire(CandidTypeOptBool{in});
}

void demo_candid_type_opts() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  // ---------------------------------------------------------------------------
  // Get the data from the wire
  std::optional<bool> in_bool;
  std::optional<float> in_float32;
  std::optional<double> in_float64;
  std::optional<int8_t> in_int8;
  std::optional<int16_t> in_int16;
  std::optional<int32_t> in_int32;
  std::optional<int64_t> in_int64;
  std::optional<__int128_t> in_int;
  std::optional<uint8_t> in_nat8;
  std::optional<uint16_t> in_nat16;
  std::optional<uint32_t> in_nat32;
  std::optional<uint64_t> in_nat64;
  std::optional<__uint128_t> in_nat;
  std::optional<std::string> in_principal;
  std::optional<std::string> in_text;

  CandidArgs args_in;
  args_in.append(CandidTypeOptBool{&in_bool});
  args_in.append(CandidTypeOptFloat32{&in_float32});
  args_in.append(CandidTypeOptFloat64{&in_float64});
  args_in.append(CandidTypeOptInt8{&in_int8});
  args_in.append(CandidTypeOptInt16{&in_int16});
  args_in.append(CandidTypeOptInt32{&in_int32});
  args_in.append(CandidTypeOptInt64{&in_int64});
  args_in.append(CandidTypeOptInt{&in_int});
  args_in.append(CandidTypeOptNat8{&in_nat8});
  args_in.append(CandidTypeOptNat16{&in_nat16});
  args_in.append(CandidTypeOptNat32{&in_nat32});
  args_in.append(CandidTypeOptNat64{&in_nat64});
  args_in.append(CandidTypeOptNat{&in_nat});
  args_in.append(CandidTypeOptPrincipal{&in_principal});
  args_in.append(CandidTypeOptText{&in_text});

  ic_api.from_wire(args_in);

  // ---------------------------------------------------------------------------
  // debug_print the data
  if (in_bool.has_value()) {
    IC_API::debug_print("Method " + std::string(__func__) +
                        " received bool value '" +
                        std::to_string(in_bool.value()) + "'");
  } else {
    IC_API::debug_print("Method " + std::string(__func__) +
                        " did not receive a bool value");
  }

  if (in_float32.has_value()) {
    IC_API::debug_print("Method " + std::string(__func__) +
                        " received float32 value '" +
                        std::to_string(in_float32.value()) + "'");
  } else {
    IC_API::debug_print("Method " + std::string(__func__) +
                        " did not receive a float32 value");
  }

  if (in_float64.has_value()) {
    IC_API::debug_print("Method " + std::string(__func__) +
                        " received float64 value '" +
                        std::to_string(in_float64.value()) + "'");
  } else {
    IC_API::debug_print("Method " + std::string(__func__) +
                        " did not receive a float64 value");
  }

  if (in_int8.has_value()) {
    IC_API::debug_print("Method " + std::string(__func__) +
                        " received int8 value '" +
                        std::to_string(in_int8.value()) + "'");
  } else {
    IC_API::debug_print("Method " + std::string(__func__) +
                        " did not receive a int8 value");
  }

  if (in_int16.has_value()) {
    IC_API::debug_print("Method " + std::string(__func__) +
                        " received int16 value '" +
                        std::to_string(in_int16.value()) + "'");
  } else {
    IC_API::debug_print("Method " + std::string(__func__) +
                        " did not receive a int16 value");
  }

  if (in_int32.has_value()) {
    IC_API::debug_print("Method " + std::string(__func__) +
                        " received int32 value '" +
                        std::to_string(in_int32.value()) + "'");
  } else {
    IC_API::debug_print("Method " + std::string(__func__) +
                        " did not receive a int32 value");
  }

  if (in_int64.has_value()) {
    IC_API::debug_print("Method " + std::string(__func__) +
                        " received int64 value '" +
                        std::to_string(in_int64.value()) + "'");
  } else {
    IC_API::debug_print("Method " + std::string(__func__) +
                        " did not receive a int64 value");
  }

  if (in_int.has_value()) {
    IC_API::debug_print("Method " + std::string(__func__) +
                        " received int value '" +
                        IC_API::to_string_128(in_int.value()) + "'");
  } else {
    IC_API::debug_print("Method " + std::string(__func__) +
                        " did not receive a int value");
  }

  if (in_nat8.has_value()) {
    IC_API::debug_print("Method " + std::string(__func__) +
                        " received nat8 value '" +
                        std::to_string(in_nat8.value()) + "'");
  } else {
    IC_API::debug_print("Method " + std::string(__func__) +
                        " did not receive a nat8 value");
  }

  if (in_nat16.has_value()) {
    IC_API::debug_print("Method " + std::string(__func__) +
                        " received nat16 value '" +
                        std::to_string(in_nat16.value()) + "'");
  } else {
    IC_API::debug_print("Method " + std::string(__func__) +
                        " did not receive a nat16 value");
  }

  if (in_nat32.has_value()) {
    IC_API::debug_print("Method " + std::string(__func__) +
                        " received nat32 value '" +
                        std::to_string(in_nat32.value()) + "'");
  } else {
    IC_API::debug_print("Method " + std::string(__func__) +
                        " did not receive a nat32 value");
  }

  if (in_nat64.has_value()) {
    IC_API::debug_print("Method " + std::string(__func__) +
                        " received nat64 value '" +
                        std::to_string(in_nat64.value()) + "'");
  } else {
    IC_API::debug_print("Method " + std::string(__func__) +
                        " did not receive a nat64 value");
  }

  if (in_nat.has_value()) {
    IC_API::debug_print("Method " + std::string(__func__) +
                        " received nat value '" +
                        IC_API::to_string_128(in_nat.value()) + "'");
  } else {
    IC_API::debug_print("Method " + std::string(__func__) +
                        " did not receive a nat value");
  }

  if (in_principal.has_value()) {
    IC_API::debug_print("Method " + std::string(__func__) +
                        " received principal value '" + in_principal.value() +
                        "'");
  } else {
    IC_API::debug_print("Method " + std::string(__func__) +
                        " did not receive a principal value");
  }

  if (in_text.has_value()) {
    IC_API::debug_print("Method " + std::string(__func__) +
                        " received text value '" + in_text.value() + "'");
  } else {
    IC_API::debug_print("Method " + std::string(__func__) +
                        " did not receive a text value");
  }

  // ---------------------------------------------------------------------------
  // Return the data
  CandidArgs args_out;
  args_out.append(CandidTypeOptBool{in_bool});
  args_out.append(CandidTypeOptFloat32{in_float32});
  args_out.append(CandidTypeOptFloat64{in_float64});
  args_out.append(CandidTypeOptInt8{in_int8});
  args_out.append(CandidTypeOptInt16{in_int16});
  args_out.append(CandidTypeOptInt32{in_int32});
  args_out.append(CandidTypeOptInt64{in_int64});
  args_out.append(CandidTypeOptInt{in_int});
  args_out.append(CandidTypeOptNat8{in_nat8});
  args_out.append(CandidTypeOptNat16{in_nat16});
  args_out.append(CandidTypeOptNat32{in_nat32});
  args_out.append(CandidTypeOptNat64{in_nat64});
  args_out.append(CandidTypeOptNat{in_nat});
  args_out.append(CandidTypeOptPrincipal{in_principal});
  args_out.append(CandidTypeOptText{in_text});
  ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_opt.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_opt()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_opt");
void demo_candid_type_opts()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_opts");
// file: src/demo.did
service : {
    "demo_candid_type_opt" : (opt bool) -> (opt bool) query;
    "demo_candid_type_opts" : (opt bool, opt float32, opt float64, opt int8, opt int16, opt int32, opt int64, opt int, opt nat8, opt nat16, opt nat32, opt nat64, opt nat, opt principal, opt text) -> 
                              (opt bool, opt float32, opt float64, opt int8, opt int16, opt int32, opt int64, opt int, opt nat8, opt nat16, opt nat32, opt nat64, opt nat, opt principal, opt text)
                              query; 
};

CandidTypePrincipal

A class to convert between Candid principal and C++ std::string.

The value of the string follows the ID Encoding Specification.

Declaration:

// candid.h
class CandidTypePrincipal {
public:
  CandidTypePrincipal(const std::string v); 
}

Example:

/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_principal.cpp

$ dfx canister call --type idl --output idl demo demo_candid_type_principal '(principal "2ibo7-dia")'
(principal "2ibo7-dia")
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_principal received value '2ibo7-dia'

$ dfx canister call --type idl --output idl demo demo_candid_type_principals '(principal "2ibo7-dia", principal "w3gef-eqbai")'
(principal "2ibo7-dia", principal "w3gef-eqbai")
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_principals received values '2ibo7-dia' & 'w3gef-eqbai'

*/
#include "demo_candid_type_principal.h"

#include <string>

#include "ic_api.h"

void demo_candid_type_principal() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);

  // The caller's authenticated principal
  // - The calling message was signed using the caller's private key
  // - The IC verified the signature using the corresponding public key
  CandidTypePrincipal caller = ic_api.get_caller();

  // A string in the format of a Candid principal.
  // - Value follows the rules for text representation of a Candid principal
  // - It's just a string, nothing more. Not authenticated by IC using keys
  std::string in{""};
  ic_api.from_wire(CandidTypePrincipal{&in});
  IC_API::debug_print("Method " + std::string(__func__) + " received value '" +
                      in + "'");

  ic_api.to_wire(CandidTypePrincipal{in});
}

void demo_candid_type_principals() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  std::string in1{""};
  std::string in2{""};
  CandidArgs args_in;
  args_in.append(CandidTypePrincipal(&in1));
  args_in.append(CandidTypePrincipal(&in2));
  ic_api.from_wire(args_in);

  IC_API::debug_print("Method " + std::string(__func__) + " received values '" +
                      in1 + "' & '" + in2 + "'");

  CandidArgs args_out;
  args_out.append(CandidTypePrincipal(in1));
  args_out.append(CandidTypePrincipal(in2));
  ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_principal.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_principal()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_principal");
void demo_candid_type_principals()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_principals");
// file: src/demo.did
service : {
    "demo_candid_type_principal" : (principal) -> (principal) query;
    "demo_candid_type_principals" : (principal, principal) -> (principal, principal) query; 
};

CandidTypeRecord

A class to convert between Candid record and C++.

A CandidTypeRecord is a like a dictionary, constructed from multiple Candid types.

Declaration:

// candid.h
class CandidTypeRecord {
public:
  CandidTypeRecord();
  void append(uint32_t field_id, CandidType field);
  void append(std::string field_name, CandidType field);
  void append(CandidType field); 
}

Example:

/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_record.cpp

$ dfx canister call --type idl --output idl demo demo_candid_type_record '(record {"field 1" = true : bool; "field 2" = 0.1 : float32; "field 3" = 0.2 : float64; "field 4" = -8 : int8; "field 5" = -16 : int16; "field 6" = -32 : int32; "field 7" = -64 : int64; "field 8" = -128 : int; "field 9" = 8 : nat8; "field 10" = 16 : nat16; "field A" = 32 : nat32; "field B" = 64 : nat64; "field C" = 128 : nat; "field D" = principal "2ibo7-dia"; "field E" = "demo" : text;})'
(record { field 10 = 16 : nat16; field 1 = true; field 2 = 0.1 : float32; field 3 = 0.2 : float64; field 4 = -8 : int8; field 5 = -16 : int16; field 6 = -32 : int32; field 7 = -64 : int64; field 8 = -128 : int; field 9 = 8 : nat8; field A = 32 : nat32; field B = 64 : nat64; field C = 128 : nat; field D = principal "2ibo7-dia"; field E = "demo";})
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_record
 received a record with fields: 

 field 1  - bool        value '1
 field 2  - float       value '0.100000
 field 3  - double      value '0.200000
 field 4  - int8_t      value '-8
 field 5  - int16_t     value '-16
 field 6  - int32_t     value '-32
 field 7  - int64_t     value '-64
 field 8  - __int128_t  value '-128
 field 9  - uint8_t     value '8
 field 10 - uint16_t    value '16
 field A  - uint32_t    value '32
 field B  - uint64_t    value '64
 field C  - __uint128_t value '128
 field D  - std::string value '2ibo7-dia
 field E  - std::string value 'demo'

$ dfx canister call --type idl --output idl demo demo_candid_type_records '(record {"field 1A" = -8 : int8;}, record {"field 2A" = -16 : int16;})'
(record { field 1A = -8 : int8;}, record { field 2A = -16 : int16;})
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_records
 received a record with field:
 field 1A  - int8_t      value '-8
 and      a record with field:
 field 1B  - int16_t     value '-16'

*/
#include "demo_candid_type_record.h"

#include <string>

#include "ic_api.h"

void demo_candid_type_record() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  // ---------------------------------------------------------------------------
  // Get the data from the wire
  bool f1{false};
  float f2{0.0};
  double f3{0.0};
  int8_t f4{0};
  int16_t f5{0};
  int32_t f6{0};
  int64_t f7{0};
  __int128_t f8{0};
  uint8_t f9{0};
  uint16_t f10{0};
  uint32_t fa{0};
  uint64_t fb{0};
  __uint128_t fc{0};
  std::string fd{""};
  std::string fe{""};

  CandidTypeRecord r_in;
  r_in.append("field 1", CandidTypeBool{&f1});
  r_in.append("field 2", CandidTypeFloat32{&f2});
  r_in.append("field 3", CandidTypeFloat64{&f3});
  r_in.append("field 4", CandidTypeInt8{&f4});
  r_in.append("field 5", CandidTypeInt16{&f5});
  r_in.append("field 6", CandidTypeInt32{&f6});
  r_in.append("field 7", CandidTypeInt64{&f7});
  r_in.append("field 8", CandidTypeInt{&f8});
  r_in.append("field 9", CandidTypeNat8{&f9});
  r_in.append("field 10", CandidTypeNat16{&f10});
  r_in.append("field A", CandidTypeNat32{&fa});
  r_in.append("field B", CandidTypeNat64{&fb});
  r_in.append("field C", CandidTypeNat{&fc});
  r_in.append("field D", CandidTypePrincipal{&fd});
  r_in.append("field E", CandidTypeText{&fe});
  ic_api.from_wire(r_in);

  // ---------------------------------------------------------------------------
  IC_API::debug_print(
      "Method " + std::string(__func__) +
      "\n received a record with fields: \n" +
      "\n field 1  - bool        value '" + std::to_string(f1) +
      "\n field 2  - float       value '" + std::to_string(f2) +
      "\n field 3  - double      value '" + std::to_string(f3) +
      "\n field 4  - int8_t      value '" + std::to_string(f4) +
      "\n field 5  - int16_t     value '" + std::to_string(f5) +
      "\n field 6  - int32_t     value '" + std::to_string(f6) +
      "\n field 7  - int64_t     value '" + std::to_string(f7) +
      "\n field 8  - __int128_t  value '" + IC_API::to_string_128(f8) +
      "\n field 9  - uint8_t     value '" + std::to_string(f9) +
      "\n field 10 - uint16_t    value '" + std::to_string(f10) +
      "\n field A  - uint32_t    value '" + std::to_string(fa) +
      "\n field B  - uint64_t    value '" + std::to_string(fb) +
      "\n field C  - __uint128_t value '" + IC_API::to_string_128(fc) +
      "\n field D  - std::string value '" + fd +
      "\n field E  - std::string value '" + fe + "'");
  // ---------------------------------------------------------------------------

  CandidTypeRecord r_out;
  r_out.append("field 1", CandidTypeBool{f1});
  r_out.append("field 2", CandidTypeFloat32{f2});
  r_out.append("field 3", CandidTypeFloat64{f3});
  r_out.append("field 4", CandidTypeInt8{f4});
  r_out.append("field 5", CandidTypeInt16{f5});
  r_out.append("field 6", CandidTypeInt32{f6});
  r_out.append("field 7", CandidTypeInt64{f7});
  r_out.append("field 8", CandidTypeInt{f8});
  r_out.append("field 9", CandidTypeNat8{f9});
  r_out.append("field 10", CandidTypeNat16{f10});
  r_out.append("field A", CandidTypeNat32{fa});
  r_out.append("field B", CandidTypeNat64{fb});
  r_out.append("field C", CandidTypeNat{fc});
  r_out.append("field D", CandidTypePrincipal{fd});
  r_out.append("field E", CandidTypeText{fe});
  ic_api.to_wire(r_out);
}

void demo_candid_type_records() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  // ---------------------------------------------------------------------------
  // Get the data from the wire
  int8_t f1a{0};
  CandidTypeRecord r1_in;
  r1_in.append("field 1A", CandidTypeInt8{&f1a});

  int16_t f2a{0};
  CandidTypeRecord r2_in;
  r2_in.append("field 2A", CandidTypeInt16{&f2a});

  CandidArgs args_in;
  args_in.append(r1_in);
  args_in.append(r2_in);
  ic_api.from_wire(args_in);

  // ---------------------------------------------------------------------------
  IC_API::debug_print(
      "Method " + std::string(__func__) + "\n received a record with field:" +
      "\n field 1A  - int8_t      value '" + std::to_string(f1a) +
      "\n and      a record with field:" +
      "\n field 2A  - int16_t     value '" + std::to_string(f2a) + "'");

  // ---------------------------------------------------------------------------
  // Return the data
  CandidTypeRecord r1_out;
  r1_out.append("field 1A", CandidTypeInt8{f1a});

  CandidTypeRecord r2_out;
  r2_out.append("field 2A", CandidTypeInt16{f2a});

  CandidArgs args_out;
  args_out.append(r1_out);
  args_out.append(r2_out);
  ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_record.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_record()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_record");
void demo_candid_type_records()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_records");
// file: src/demo.did
service : {
    "demo_candid_type_record" : (record {"field 1" : bool; "field 2" : float32; "field 3" : float64; "field 4" : int8; "field 5" : int16; "field 6" : int32; "field 7": int64; "field 8" : int; "field 9" : nat8; "field 10" : nat16; "field A" : nat32; "field B" : nat64; "field C" : nat; "field D" : principal; "field E" : text;})   -> 
                                (record {"field 1" : bool; "field 2" : float32; "field 3" : float64; "field 4" : int8; "field 5" : int16; "field 6" : int32; "field 7": int64; "field 8" : int; "field 9" : nat8; "field 10" : nat16; "field A" : nat32; "field B" : nat64; "field C" : nat; "field D" : principal; "field E" : text;}) 
                                query;
    "demo_candid_type_records" : (record {"field 1A" : int8;}, record {"field 2A" : int16;}) -> 
                                 (record {"field 1A" : int8;}, record {"field 2A" : int16;}) 
                                 query; 
};

CandidTypeText

A class to convert between Candid text and C++ std::string.

Declaration:

// candid.h
class CandidTypeText {
public:
  CandidTypeText(std::string *v);
  CandidTypeText(const std::string v); 
}

Example:

/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_text.cpp

$ dfx canister call --type idl --output idl demo demo_candid_type_text '("demo A" : text)'
("demo A")
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_text received value 'demo A'

$ dfx canister call --type idl --output idl demo demo_candid_type_texts '("demo A" : text, "demo B" : text)'
("demo A", "demo B")
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_texts received values 'demo A' & 'demo B'

*/
#include "demo_candid_type_text.h"

#include <string>

#include "ic_api.h"

void demo_candid_type_text() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  std::string in{""};
  ic_api.from_wire(CandidTypeText{&in});
  IC_API::debug_print("Method " + std::string(__func__) + " received value '" +
                      in + "'");

  ic_api.to_wire(CandidTypeText{in});
}

void demo_candid_type_texts() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  std::string in1{""};
  std::string in2{""};
  CandidArgs args_in;
  args_in.append(CandidTypeText(&in1));
  args_in.append(CandidTypeText(&in2));
  ic_api.from_wire(args_in);

  IC_API::debug_print("Method " + std::string(__func__) + " received values '" +
                      in1 + "' & '" + in2 + "'");

  CandidArgs args_out;
  args_out.append(CandidTypeText(in1));
  args_out.append(CandidTypeText(in2));
  ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_text.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_text()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_text");
void demo_candid_type_texts()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_texts");
// file: src/demo.did
service : {
    "demo_candid_type_text" : (text) -> (text) query;
    "demo_candid_type_texts" : (text, text) -> (text, text) query; 
};

CandidTypeVariant

A class to convert between Candid variant and C++.

A CandidTypeVariant is similar to the CandidTypeRecord, but it can only store one 'label: value' out of multiple allowed variants.

Declaration:

// candid.h
class CandidTypeVariant {
public:
  CandidTypeVariant();
  CandidTypeVariant(std::string *p_label);
  CandidTypeVariant(const std::string label);
  CandidTypeVariant(const std::string label, const CandidType field); 
}

Example:

/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_variant.cpp

$ dfx canister call --type idl --output idl demo demo_candid_type_variant '(variant {"field 2" = 0.1 : float32;})'
(variant { field 2 = 0.1 : float32;})
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_variant
    received a variant with label: field 2

    field 2  - float       value '0.100000

$ dfx canister call --type idl --output idl demo demo_candid_type_variants '(variant {"field 1" = true : bool;}, variant {"field 2" = 0.1 : float32;})'
(variant {"field 1" = true : bool;}, variant {"field 2" = 0.1 : float32;})
-> check the console of the local network. The canister will print:
   [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_variants

    received a variant with label: field 2

    field 2  - float       value '0.100000

    received another variant with label: field 2

    field 2  - float       value '0.100000

*/
#include "demo_candid_type_variant.h"

#include <string>

#include "ic_api.h"

void demo_candid_type_variant() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  // ---------------------------------------------------------------------------
  // Get the data from the wire
  bool f1{false};
  float f2{0.0};
  double f3{0.0};
  int8_t f4{0};
  int16_t f5{0};
  int32_t f6{0};
  int64_t f7{0};
  __int128_t f8{0};
  uint8_t f9{0};
  uint16_t f10{0};
  uint32_t fa{0};
  uint64_t fb{0};
  __uint128_t fc{0};
  std::string fd{""};
  std::string fe{""};

  std::string label{""};
  CandidTypeVariant v_in(&label);
  v_in.append("field 1", CandidTypeBool{&f1});
  v_in.append("field 2", CandidTypeFloat32{&f2});
  v_in.append("field 3", CandidTypeFloat64{&f3});
  v_in.append("field 4", CandidTypeInt8{&f4});
  v_in.append("field 5", CandidTypeInt16{&f5});
  v_in.append("field 6", CandidTypeInt32{&f6});
  v_in.append("field 7", CandidTypeInt64{&f7});
  v_in.append("field 8", CandidTypeInt{&f8});
  v_in.append("field 9", CandidTypeNat8{&f9});
  v_in.append("field 10", CandidTypeNat16{&f10});
  v_in.append("field A", CandidTypeNat32{&fa});
  v_in.append("field B", CandidTypeNat64{&fb});
  v_in.append("field C", CandidTypeNat{&fc});
  v_in.append("field D", CandidTypePrincipal{&fd});
  v_in.append("field E", CandidTypeText{&fe});
  ic_api.from_wire(v_in);

  // ---------------------------------------------------------------------------
  // Debug print of the values

  std::string msg;
  msg.append("Method " + std::string(__func__) +
             "\n received a variant with label: " + label + "\n");

  if (label == "field 1") {
    msg.append("\n field 1  - bool        value '" + std::to_string(f1));
  } else if (label == "field 2") {
    msg.append("\n field 2  - float       value '" + std::to_string(f2));
  } else if (label == "field 3") {
    msg.append("\n field 3  - double      value '" + std::to_string(f3));
  } else if (label == "field 4") {
    msg.append("\n field 4  - int8_t      value '" + std::to_string(f4));
  } else if (label == "field 5") {
    msg.append("\n field 5  - int16_t     value '" + std::to_string(f5));
  } else if (label == "field 6") {
    msg.append("\n field 6  - int32_t     value '" + std::to_string(f6));
  } else if (label == "field 7") {
    msg.append("\n field 7  - int64_t     value '" + std::to_string(f7));
  } else if (label == "field 8") {
    msg.append("\n field 8  - __int128_t  value '" + IC_API::to_string_128(f8));
  } else if (label == "field 9") {
    msg.append("\n field 9  - uint8_t     value '" + std::to_string(f9));
  } else if (label == "field 10") {
    msg.append("\n field 10 - uint16_t    value '" + std::to_string(f10));
  } else if (label == "field A") {
    msg.append("\n field A  - uint32_t    value '" + std::to_string(fa));
  } else if (label == "field B") {
    msg.append("\n field B  - uint64_t    value '" + std::to_string(fb));
  } else if (label == "field C") {
    msg.append("\n field C  - __uint128_t value '" + IC_API::to_string_128(fc));
  } else if (label == "field D") {
    msg.append("\n field D  - std::string value '" + fd);
  } else if (label == "field E") {
    msg.append("\n field E  - std::string value '" + fe);
  }

  IC_API::debug_print(msg);

  // ---------------------------------------------------------------------------
  // Return the data
  CandidTypeVariant v_out{label};
  if (label == "field 1") {
    v_out.append("field 1", CandidTypeBool{f1});
  } else if (label == "field 2") {
    v_out.append("field 2", CandidTypeFloat32{f2});
  } else if (label == "field 3") {
    v_out.append("field 3", CandidTypeFloat64{f3});
  } else if (label == "field 4") {
    v_out.append("field 4", CandidTypeInt8{f4});
  } else if (label == "field 5") {
    v_out.append("field 5", CandidTypeInt16{f5});
  } else if (label == "field 6") {
    v_out.append("field 6", CandidTypeInt32{f6});
  } else if (label == "field 7") {
    v_out.append("field 7", CandidTypeInt64{f7});
  } else if (label == "field 8") {
    v_out.append("field 8", CandidTypeInt{f8});
  } else if (label == "field 9") {
    v_out.append("field 9", CandidTypeNat8{f9});
  } else if (label == "field 10") {
    v_out.append("field 10", CandidTypeNat16{f10});
  } else if (label == "field A") {
    v_out.append("field A", CandidTypeNat32{fa});
  } else if (label == "field B") {
    v_out.append("field B", CandidTypeNat64{fb});
  } else if (label == "field C") {
    v_out.append("field C", CandidTypeNat{fc});
  } else if (label == "field D") {
    v_out.append("field D", CandidTypePrincipal{fd});
  } else if (label == "field E") {
    v_out.append("field E", CandidTypeText{fe});
  }
  ic_api.to_wire(v_out);
}

void demo_candid_type_variants() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  // ---------------------------------------------------------------------------
  // Get the data from the wire for two variants

  // variant a
  bool f1a{false};
  float f2a{0.0};

  std::string labela{""};
  CandidTypeVariant va_in(&labela);
  va_in.append("field 1", CandidTypeBool{&f1a});
  va_in.append("field 2", CandidTypeFloat32{&f2a});

  // variant b
  bool f1b{false};
  float f2b{0.0};

  std::string labelb{""};
  CandidTypeVariant vb_in(&labelb);
  vb_in.append("field 1", CandidTypeBool{&f1b});
  vb_in.append("field 2", CandidTypeFloat32{&f2b});

  CandidArgs args_in;
  args_in.append(va_in);
  args_in.append(vb_in);
  ic_api.from_wire(args_in);

  // ---------------------------------------------------------------------------
  // Debug print of the values

  // variant a
  std::string msg;
  msg.append("Method " + std::string(__func__) +
             "\n received a variant with label: " + labela + "\n");

  if (labela == "field 1") {
    msg.append("\n field 1  - bool        value '" + std::to_string(f1a));
  } else if (labela == "field 2") {
    msg.append("\n field 2  - float       value '" + std::to_string(f2a));
  }

  // variant b
  msg.append("\n\n received another variant with label: " + labelb + "\n");

  if (labelb == "field 1") {
    msg.append("\n field 1  - bool        value '" + std::to_string(f1b));
  } else if (labelb == "field 2") {
    msg.append("\n field 2  - float       value '" + std::to_string(f2b));
  }

  IC_API::debug_print(msg);

  // ---------------------------------------------------------------------------
  // Return the data

  // variant a
  CandidTypeVariant va_out{labela};
  if (labela == "field 1") {
    va_out.append("field 1", CandidTypeBool{f1a});
  } else if (labela == "field 2") {
    va_out.append("field 2", CandidTypeFloat32{f2a});
  }

  // variant b
  CandidTypeVariant vb_out{labelb};
  if (labelb == "field 1") {
    vb_out.append("field 1", CandidTypeBool{f1b});
  } else if (labelb == "field 2") {
    vb_out.append("field 2", CandidTypeFloat32{f2b});
  }

  CandidArgs args_out;
  args_out.append(va_out);
  args_out.append(vb_out);
  ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_variant.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_variant()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_variant");
void demo_candid_type_variants()
    WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_variants");
// file: src/demo.did
service : {
    "demo_candid_type_variant" : (variant {"field 1" : bool; "field 2" : float32; "field 3" : float64; "field 4" : int8; "field 5" : int16; "field 6" : int32; "field 7": int64; "field 8" : int; "field 9" : nat8; "field 10" : nat16; "field A" : nat32; "field B" : nat64; "field C" : nat; "field D" : principal; "field E" : text;})   -> 
                                 (variant {"field 1" : bool; "field 2" : float32; "field 3" : float64; "field 4" : int8; "field 5" : int16; "field 6" : int32; "field 7": int64; "field 8" : int; "field 9" : nat8; "field 10" : nat16; "field A" : nat32; "field B" : nat64; "field C" : nat; "field D" : principal; "field E" : text;}) 
                                 query;
    "demo_candid_type_variants" : (variant {"field 1" : bool; "field 2" : float32;}, variant {"field 1" : bool; "field 2" : float32;}) -> 
                                  (variant {"field 1" : bool; "field 2" : float32;}, variant {"field 1" : bool; "field 2" : float32;})
                                  query; 
};

CandidTypeVec

A group of classes to convert between Candid vec candid-type and C++ std::vector<C++ type>.

CandidTypeVec Candid C++ Example
CandidTypeVecBool vec bool std::vector<bool>
not supported vec empty not supported
CandidTypeVecFloat32 vec float32 std::vector<float>
CandidTypeVecFloat64 vec float64 std::vector<double>
CandidTypeVecInt8 vec int8 std::vector<int8_t>
CandidTypeVecInt16 vec int16 std::vector<int16_t>
CandidTypeVecInt32 vec int32 std::vector<int32_t>
CandidTypeVecInt64 vec int64 std::vector<int64_t>
CandidTypeVecInt vec int std::vector<__int128_t>
CandidTypeVecNat8 vec nat8 std::vector<uint8_t>
CandidTypeVecNat16 vec nat16 std::vector<uint16_t>
CandidTypeVecNat32 vec nat32 std::vector<uint32_t>
CandidTypeVecNat64 vec nat64 std::vector<uint64_t>
CandidTypeVecNat vec nat std::vector<__uint128_t>
not supported vec null not supported
CandidTypeVecPrincipal vec principal std::vector<std::string>
CandidTypeVecText vec text std::vector<std::string>

🚧 This documentation section is currently under construction! 🚧


IC_API

When developing a C++ Smart Contract for the Internet Computer, you engage with the canister system API using the IC_API. Among other things, it facilitates:

  • Verifying the identity of the caller
  • Acquiring incoming data from the wire
  • Dispatching data back to the wire
  • Printing to the console, when deployed to the local network
  • Getting the Internet Computer system time
  • Trapping a canister to stop execution and return an error

The message data is organized in the Candid format, and the IC_API seamlessly aligns this with standard C++ data structures.


ic0 system interface

The methods detailed in this section encapsulate the functionality of the ic0 system interface.

As a C++ developer, you can interact with the Internet Computer Interface seamlessly, without delving into its intricacies, thanks to the abstraction provided by icpp-pro's C++ interface methods.


debug_print

Declaration:

// ic_api.h
class IC_API {
public:
  static void debug_print(const char *msg);
  static void debug_print(const std::string &msg); 
}

The debug_print method is a utility function used to print debug messages during the local execution of your application. This is helpful for tracing and debugging your application's flow. The debug_print method accepts a C-style string (const char *) or a C++ string (const std::string &).

Note that the debug_print messages only appear during local development and testing. In a production environment on the IC mainnet, these messages are disregarded.

Example:

/* file: src/demo_debug_print.cpp

$ dfx canister call --type idl --output idl demo demo_debug_print '()'
...nothing is printed here

-> check the console of the local network. The canister will print:
   Hello expmt-gtxsw-inftj-ttabj-qhp5s-nozup-n3bbo-k7zvn-dg4he-knac3-lae

-> when deployed to IC's mainnet, nothing is printed.

*/
#include "demo_debug_print.h"
#include "ic_api.h"

void demo_debug_print() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  IC_API::debug_print("Hello " + caller.get_text());

  ic_api.from_wire();
  ic_api.to_wire();
}
// file: src/demo_debug_print.h
#pragma once
#include "wasm_symbol.h"
void demo_debug_print() WASM_SYMBOL_EXPORTED("canister_query demo_debug_print");
// file: src/demo.did
service : {
    "demo_debug_print": () -> () query; 
};

In this example, the debug_print() method is used to display a greeting message containing the caller's principal. The demo_debug_print() method retrieves the CandidTypePrincipal of the caller using theget_caller() method, then it creates a message and passes it to debug_print().

The demo.did file describes the interface of the service in the Candid Interface Description Language. It declares one query function, demo_debug_print, accepting no arguments and returning no values.


IC_API constructor

Declaration:

// ic_api.h
class IC_API {
public:
  IC_API(const CanisterBase &canister_entry,
         const bool &debug_print); 
}

This constructor creates an IC_API instance and initializes the debug printing flag according to the debug_print parameter passed to it.

Example:

Here's an example of how to create an IC_API instance at the start of your public C++ method:

/* file: src/demo_ic_api.cpp

$ dfx canister call --type idl --output idl demo demo_ic_api '()'

*/
#include "demo_ic_api.h"
#include "ic_api.h"

void demo_ic_api() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();
  ic_api.from_wire();
  ic_api.to_wire();
}
// file: src/demo_ic_api.h
#pragma once
#include "wasm_symbol.h"
void demo_ic_api() WASM_SYMBOL_EXPORTED("canister_query demo_ic_api");
// file: src/demo.did
service : {
    "demo_ic_api": () -> () query; 
};

In this example, an IC_API object named ic_api is created with the debug_print flag set to false. If the debug_print attribute is toggled to true, it results in more detailed console outputs during local execution. However, this flag is disregarded when the application operates in a production environment on the IC mainnet.

The demo_ic_api canister method does not expect an argument, and does not return an argument. This is expressed by the empty argument lists for the from_wire & to_wire methods.

The from_wire method is not required, but recommended, because it will verify that the incoming message indeed has zero arguments, and if not, it will trap explicitly and reject the message.

The to_wire method is required to return the response. If omitted, the canister will will return Error: Failed query call, with message that the canister did not reply to the call.

The demo.did file describes the interface of the service in the Candid Interface Description Language. It declares one query function, demo_ic_api, accepting no arguments and returning no values.


get_caller

Declaration:

// ic_api.h
class IC_API {
public:
  CandidTypePrincipal get_caller(); 
}

This method returns the principal of the caller. The return type is CandidTypePrincipal.

In the Internet Computer, principals are unique identifiers. They are derived from a public key and are cryptographically secure. When a user (or another canister) calls a canister method, they sign the message with their private key. The Internet Computer then uses the corresponding public key to verify the signature and thus the authenticity of the call.

Example:

Here is how to use get_caller() in your C++ method:

/* file: src/demo_get_caller.cpp

$ dfx canister call --type idl --output idl demo demo_get_caller '()'
(Hello! Your principal is expmt-gtxsw-inftj-ttabj-qhp5s-nozup-n3bbo-k7zvn-dg4he-knac3-lae)

*/
#include "demo_get_caller.h"
#include "ic_api.h"

void demo_get_caller() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);

  // The caller's authenticated principal
  // - The calling message was signed using the caller's private key
  // - The IC verified the signature using the corresponding public key
  CandidTypePrincipal caller = ic_api.get_caller();

  ic_api.from_wire();

  std::string msg;
  msg.append("Hello! Your principal is ");
  msg.append(caller.get_text());

  ic_api.to_wire(CandidTypeText(msg));
}
// file: src/demo_get_caller.h
#pragma once
#include "wasm_symbol.h"
void demo_get_caller() WASM_SYMBOL_EXPORTED("canister_query demo_get_caller");
// file: src/demo.did
service : {
    "demo_get_caller": () -> (text) query; 
};

In this example, get_caller() is used to retrieve the CandidTypePrincipal representing the caller. This could be useful in methods where you need to identify or verify the caller.

The demo.did file describes the interface of the service in the Candid Interface Description Language. It declares one query function, demo_get_caller, accepting no arguments and returning a Candid text value.


get_canister_self

🚧 This documentation section is currently under construction! 🚧

Returns the principal (id) of the canister.

See QA canister_1 for an example.


get_canister_self_cycle_balance

🚧 This documentation section is currently under construction! 🚧

Returns the cycle balance of the canister.

See QA canister_1 for an example.


from_wire

Declaration:

// ic_api.h
class IC_API {
public:
  void from_wire();
  void from_wire(CandidType arg_in);
  void from_wire(CandidArgs args_in);
  void from_wire(IC_HttpRequest &request);
  void from_wire(IC_HttpUpdateRequest &request); 
}

Receives data from the wire in Candid format, assuming either no arguments, a single argument of a CandidType object, or a CandidArgs object to receive multiple arguments.

C++ data structures are passed into the CandidType objects as pointers, and the from_wire will deserialize the Candid byte stream and map the values on them.

Example:

Here is how to use from_wire() in your C++ method:

/* file: src/demo_from_wire.cpp

$ dfx canister call --type idl --output idl demo demo_from_wire_no_arg '()'


$ dfx canister call --type idl --output idl demo demo_from_wire_one_arg '("Neuron Staking")'
("Hello expmt-gtxsw-inftj-ttabj-qhp5s-nozup-n3bbo-k7zvn-dg4he-knac3-lae, your hobby is Neuron Staking")

$ dfx canister call --type idl --output idl demo demo_from_wire_multiple_args '("Neuron Staking", 3000 : nat64)'
("Hello expmt-gtxsw-inftj-ttabj-qhp5s-nozup-n3bbo-k7zvn-dg4he-knac3-lae, you earned 3000 ICP from your hobby, Neuron Staking")

*/
#include "demo_from_wire.h"
#include "ic_api.h"

void demo_from_wire_no_arg() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  ic_api.from_wire();
  ic_api.to_wire();
}

void demo_from_wire_one_arg() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  std::string hobby{""};
  ic_api.from_wire(CandidTypeText{&hobby});

  std::string msg;
  msg.append("Hello " + caller.get_text() + ", ");
  msg.append("your hobby is " + hobby);

  ic_api.to_wire(CandidTypeText(msg));
}

void demo_from_wire_multiple_args() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  std::string hobby{""};
  uint64_t num_icp{0};
  CandidArgs args_in;
  args_in.append(CandidTypeText(&hobby));
  args_in.append(CandidTypeNat64(&num_icp));
  ic_api.from_wire(args_in);

  std::string msg;
  msg.append("Hello " + caller.get_text() + ", ");
  msg.append("you earned " + std::to_string(num_icp) +
             " ICP from your hobby, " + hobby);
  ic_api.to_wire(CandidTypeText(msg));
}
// file: src/demo_from_wire.h
#pragma once
#include "wasm_symbol.h"
void demo_from_wire_no_arg()
    WASM_SYMBOL_EXPORTED("canister_query demo_from_wire_no_arg");
void demo_from_wire_one_arg()
    WASM_SYMBOL_EXPORTED("canister_query demo_from_wire_one_arg");
void demo_from_wire_multiple_args()
    WASM_SYMBOL_EXPORTED("canister_query demo_from_wire_multiple_args");
// file: src/demo.did
service : {
    "demo_from_wire_no_arg": () -> () query;
    "demo_from_wire_one_arg": (text) -> (text) query;
    "demo_from_wire_multiple_args": (text, nat64) -> (text) query; 
};

In this example, demo_from_wire() is used to receive and process data in various scenarios - with no arguments, with one argument, and with multiple arguments.

The demo_from_wire_no_arg() function simply creates an IC_API instance, calls from_wire() without any arguments, and then calls to_wire(). This is a basic use case where no data is received or sent over the wire.

The demo_from_wire_one_arg() function, on the other hand, receives a single CandidTypeText object. The from_wire() function is called with this object, which then deserializes the incoming Candid byte stream and maps the value onto the hobby string. After creating a response message using the received hobby and the caller's principal, it calls to_wire() to send the response.

Lastly, the demo_from_wire_multiple_args() function handles a scenario where multiple arguments are received. It defines a CandidArgs object, appends the expected arguments to that, and calls from_wire() with this object. The function then constructs a response using the received hobby and the number of ICP earned from it, and calls to_wire() to send this response.

This demonstrates how from_wire() can be used effectively to receive and process data in a variety of use cases in your C++ methods.

The demo.did file describes the interface of the service in the Candid Interface Description Language. It declares the three query functions and specifies the Candid types each functions accepts as arguments and returns as values.


is_controller

🚧 This documentation section is currently under construction! 🚧

Returns true if the principal passed as argument is a controller of the canister.

See QA canister_1 for an example.


time

Declaration:

// ic_api.h
class IC_API {
public:
  static uint64_t time(); 
}

The time method is used to retrieve the current system time from the Internet Computer. The time is returned as a uint64_t representing the number of nanoseconds since the Unix epoch (1970-01-01). The time, as observed by the canister, is guaranteed to be monotonically increasing, even across canister upgrades. Additionally, within a single invocation of one entry point, the time is constant.

Please note that due to the decentralized nature of the Internet Computer, the system times of different canisters are unrelated and there is no notion of a timezone. Hence, calls from one canister to another may appear to travel “backwards in time”.

Example:

Here is how to use time() in your C++ method:

/* file: src/demo_time.cpp

$ dfx canister call --type idl --output idl demo demo_time '()'
("The current system time in nanoseconds: 1684265399218380314 (2023-5-16 19:29:59)")

*/
#include "demo_time.h"
#include "ic_api.h"
#include <ctime>
#include <sstream>

std::string format_time(uint64_t time_in_ns) {
  /* 
  https://internetcomputer.org/docs/current/references/ic-interface-spec#system-api-time
  The time is given as nanoseconds since 1970-01-01. 
  - The IC guarantees that the time, as observed by the canister, is monotonically increasing, 
    even across canister upgrades. 
  - Within an invocation of one entry point, the time is constant. 
  - The system times of different canisters are unrelated, and calls from one canister to another 
    may appear to travel “backwards in time”.
  - The IC is decentralized and has no notion of a timezone.
  */
  std::time_t time_in_s = time_in_ns / 1'000'000'000;
  std::tm *tm = std::gmtime(&time_in_s);

  // Format the time manually
  std::string formatted_time = "";
  formatted_time += std::to_string(tm->tm_year + 1900); // Year
  formatted_time += "-";
  formatted_time += std::to_string(tm->tm_mon + 1);     // Month
  formatted_time += "-";
  formatted_time += std::to_string(tm->tm_mday);        // Day
  formatted_time += " ";
  formatted_time += std::to_string(tm->tm_hour);        // Hour
  formatted_time += ":";
  formatted_time += std::to_string(tm->tm_min);         // Minute
  formatted_time += ":";
  formatted_time += std::to_string(tm->tm_sec);         // Second

  return formatted_time;
}

void demo_time() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  uint64_t time_in_ns = ic_api.time();

  std::string msg;
  msg.append("The current system time in nanoseconds: " +
             std::to_string(time_in_ns) + " (" + format_time(time_in_ns) + ")");

  IC_API::debug_print(msg);
  ic_api.to_wire(CandidTypeText(msg));
}
// file: src/demo_time.h
#pragma once
#include "wasm_symbol.h"
void demo_time() WASM_SYMBOL_EXPORTED("canister_query demo_time");
// file: src/demo.did
service : {
    "demo_time": () -> (text) query; 
};

In this example, the time() method is used to retrieve the current system time in nanoseconds. The demo_time() method formats this time into a human-readable string and sends it back as a response to the caller.

The demo.did file describes the interface of the service in the Candid Interface Description Language. It declares one query function, demo_time, accepting no arguments and returning a Candid text value.


to_wire

Declaration:

// ic_api.h
class IC_API {
public:
  void to_wire();
  void to_wire(const CandidType &arg_out);
  void to_wire(const CandidArgs &args_out);
  void to_wire(const IC_HttpResponse response); 
}

Sends data to the wire in Candid format, assuming either no arguments, a single argument of a CandidType object, or aCandidArgs object to send multiple arguments.

C++ data structures are passed into the CandidType objects, and the to_wirefunction will serialize these values into a Candid byte stream to be sent over the wire.

Example:

Here is how to use to_wire() in your C++ method:

/* file: src/demo_to_wire.cpp

$ dfx canister call --type idl --output idl demo demo_to_wire_no_arg '()'


$ dfx canister call --type idl --output idl demo demo_to_wire_one_arg '("Neuron Staking")'
("Hello expmt-gtxsw-inftj-ttabj-qhp5s-nozup-n3bbo-k7zvn-dg4he-knac3-lae, your hobby is Neuron Staking")

$ dfx canister call --type idl --output idl demo demo_to_wire_multiple_args '("Neuron Staking", 3000 : nat64)'
("expmt-gtxsw-inftj-ttabj-qhp5s-nozup-n3bbo-k7zvn-dg4he-knac3-lae", "Neuron Staking", 3_000 : nat64, "ICP")

*/
#include "demo_to_wire.h"
#include "ic_api.h"

void demo_to_wire_no_arg() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  ic_api.from_wire();
  ic_api.to_wire();
}

void demo_to_wire_one_arg() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  std::string hobby{""};
  ic_api.from_wire(CandidTypeText{&hobby});

  std::string msg;
  msg.append("Hello " + caller.get_text() + ", ");
  msg.append("your hobby is " + hobby);

  ic_api.to_wire(CandidTypeText(msg));
}

void demo_to_wire_multiple_args() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  std::string hobby{""};
  uint64_t num_icp{0};
  CandidArgs args_in;
  args_in.append(CandidTypeText(&hobby));
  args_in.append(CandidTypeNat64(&num_icp));
  ic_api.from_wire(args_in);

  CandidArgs args_out;
  args_out.append(CandidTypeText(caller.get_text()));
  args_out.append(CandidTypeText(hobby));
  args_out.append(CandidTypeNat64(num_icp));
  args_out.append(CandidTypeText("ICP"));
  ic_api.to_wire(args_out);
}
// file: src/demo_to_wire.h
#pragma once
#include "wasm_symbol.h"
void demo_to_wire_no_arg()
    WASM_SYMBOL_EXPORTED("canister_query demo_to_wire_no_arg");
void demo_to_wire_one_arg()
    WASM_SYMBOL_EXPORTED("canister_query demo_to_wire_one_arg");
void demo_to_wire_multiple_args()
    WASM_SYMBOL_EXPORTED("canister_query demo_to_wire_multiple_args");
// file: src/demo.did
service : {
    "demo_to_wire_no_arg": () -> () query;
    "demo_to_wire_one_arg": (text) -> (text) query;
    "demo_to_wire_multiple_args": (text, nat64) -> (text, text, nat64, text) query; 
};

In this example, to_wire() is used to send data in various scenarios - with no arguments, with one argument, and with multiple arguments.

The demo_to_wire_no_arg() function creates an IC_API instance, calls from_wire() without any arguments, and then calls to_wire(). This is a basic use case where no data is received or sent over the wire.

The demo_to_wire_one_arg() function receives and sends a single CandidTypeText object. The from_wire() function is called with this object, which then deserializes the incoming Candid byte stream and maps the value onto the hobby string. After creating a response message using the received hobby and the caller's principal, it calls to_wire() to send the response.

Lastly, the demo_to_wire_multiple_args() function handles a scenario where multiple arguments are received and send. It defines a CandidArgs object, appends the expected arguments to that, and calls from_wire() with this object. After constructing a response using the received hobby and the number of ICP earned from it, it calls to_wire() to send this response as a CandidArgs object.

This demonstrates how to_wire() can be effectively used to send data in a variety of use cases in your C++ methods.

The demo.did file describes the interface of the service in the Candid Interface Description Language. It declares the three query functions and specifies the Candid types each functions accepts as arguments and returns as values.


trap

Declaration:

// ic_api.h
class IC_API {
public:
  static void trap(const char *msg);
  static void trap(const std::string &msg); 
}

The trap method is a utility function that causes the canister's execution to be immediately terminated. It takes a string as an argument, which is used as the error message that's returned to the caller. This method can be called in situations where an unrecoverable error has occurred and you wish to halt execution and report an error to the user. The trap method accepts a C-style string (const char *) or a C++ string (const std::string &).

Example:

Here is how to use trap() in your C++ method:

/* file: src/demo_trap.cpp

$ dfx canister call --type idl --output idl demo demo_trap '()'
Error: Failed query call.
Caused by: Failed query call.
  The Replica returned an error: code 5, message: "IC0503: Canister bkyz2-fmaaa-aaaaa-qaaaq-cai trapped explicitly:
  Hello expmt-gtxsw-inftj-ttabj-qhp5s-nozup-n3bbo-k7zvn-dg4he-knac3-lae
  This is a trap demo."

*/
#include "demo_trap.h"
#include "ic_api.h"

void demo_trap() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  IC_API::trap("\n   Hello " + caller.get_text() + "\n   This is a trap demo.");
}
// file: src/demo_trap.h
#pragma once
#include "wasm_symbol.h"
void demo_trap() WASM_SYMBOL_EXPORTED("canister_query demo_trap");
// file: src/demo.did
service : {
    "demo_trap": () -> () query; 
};

In this example, the trap() method is used to halt the execution of the canister and return an error message to the caller. The demo_trap() method retrieves the CandidTypePrincipal of the caller using the get_caller() method, then it creates an error message and passes it to trap().

The demo.did file describes the interface of the service in the Candid Interface Description Language. It declares one query function, demo_trap, accepting no arguments and returning no values.


utilities

This section outlines various utility functions designed to streamline the process of interacting with data structures within C++ smart contracts developed using icpp-pro.


string_to_uint128_t and string_to_int128_t

Declaration:

// ic_api.h
class IC_API {
public:
  static std::optional<__uint128_t> string_to_uint128_t(const std::string &str);
  static std::optional<__int128_t> string_to_int128_t(const std::string &str); 
}

The string_to_uint128_t & string_to_int128_t functions serve to transform std::string values into __uint128_t and __int128_t format.

As clang++ compiler extensions, these types cannot be initialized from string literals, thus necessitating these custom functions.

Example:

/* file: src/demo_string_to_int128.cpp

$ dfx canister call --type idl --output idl demo demo_string_to_int128 '()'
...nothing is printed here

-> check the console of the local network. The canister will print:
  [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] max__uint128_t=340282366920938463463374607431768211455
  [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] max__int128_t=170141183460469231731687303715884105727
  [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] min__int128_t=-170141183460469231731687303715884105727

-> when deployed to IC's mainnet, nothing is printed.

*/
#include "demo_string_to_int128.h"

#include <optional>
#include <string>

#include "ic_api.h"

void demo_string_to_int128() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  // ----------------------------------------------------------------------------
  // Numbers larger than uint64_t & int64_t can NOT be initialized with a literal
  // - Use IC_API utility methods `string_to_uint128_t` & `string_to_int128_t`,
  //   to initialize them from a string.
  // - These utility methods return an `std::optional`, which succeeds only if the
  //   conversion is successful.

  std::optional<__uint128_t> max__uint128_t = IC_API::string_to_uint128_t(
      "340282366920938463463374607431768211455");  // 2^128 - 1
  std::optional<__int128_t> max__int128_t = IC_API::string_to_int128_t(
      "170141183460469231731687303715884105727");  // +2^127 - 1
  std::optional<__int128_t> min__int128_t = IC_API::string_to_int128_t(
      "-170141183460469231731687303715884105727"); // -2^127 - 1

  // Now we first check the content of the std::optional, before using to_string_128
  // This is a roundtrip from string -> value -> string
  if (max__uint128_t.has_value()) {
    IC_API::debug_print("max__uint128_t=" +
                        IC_API::to_string_128(max__uint128_t.value()));
  } else {
    IC_API::trap("ERROR: string_to_uint128_t failed for max__uint128_t");
  }

  if (max__int128_t.has_value()) {
    IC_API::debug_print("max__int128_t=" +
                        IC_API::to_string_128(max__int128_t.value()));
  } else {
    IC_API::trap("ERROR: string_to_int128_t failed for max__int128_t");
  }

  if (min__int128_t.has_value()) {
    IC_API::debug_print("min__int128_t=" +
                        IC_API::to_string_128(min__int128_t.value()));
  } else {
    IC_API::trap("ERROR: string_to_int128_t failed for min__int128_t");
  }
  // ----------------------------------------------------------------------------

  ic_api.from_wire();
  ic_api.to_wire();
}
// file: src/demo_string_to_int128.h
#pragma once
#include "wasm_symbol.h"
void demo_string_to_int128()
    WASM_SYMBOL_EXPORTED("canister_query demo_string_to_int128");
// file: src/demo.did
service : {
    "demo_string_to_int128": () -> () query; 
};

to_string_128

Declaration:

// ic_api.h
class IC_API {
public:
  static std::string to_string_128(__uint128_t v);
  static std::string to_string_128(__int128_t v); 
}

The to_string_128 function serves to transform __uint128_t and __int128_t values into std::string format. As clang++ compiler extensions, these types are not handled by std::to_string, thus necessitating this custom function.

Example:

/* file: src/demo_to_string_128.cpp

$ dfx canister call --type idl --output idl demo demo_to_string_128 '()'
...nothing is printed here

-> check the console of the local network. The canister will print:
  [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] v1 =101
  [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] v2 =102
  [Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] v3 =-103

-> when deployed to IC's mainnet, nothing is printed.

*/
#include "demo_to_string_128.h"

#include <optional>
#include <string>

#include "ic_api.h"

void demo_to_string_128() {
  IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
  CandidTypePrincipal caller = ic_api.get_caller();

  // -------------------------------------------------------------------------
  // Numbers smaller than uint64_t & int64_t can be initialized with a literal
  __uint128_t v1{101};
  __int128_t v2{102};
  __int128_t v3{-103};

  // To create a string, use the IC_API utility method `to_string_128`
  IC_API::debug_print("v1 =" + IC_API::to_string_128(v1));
  IC_API::debug_print("v2 =" + IC_API::to_string_128(v2));
  IC_API::debug_print("v3 =" + IC_API::to_string_128(v3));

  // ----------------------------------------------------------------------------
  // Numbers larger than uint64_t & int64_t can NOT be initialized with a literal
  // See `demo_string_to_int128.cpp`

  ic_api.from_wire();
  ic_api.to_wire();
}
// file: src/demo_to_string_128.h
#pragma once
#include "wasm_symbol.h"
void demo_to_string_128()
    WASM_SYMBOL_EXPORTED("canister_query demo_to_string_128");
// file: src/demo.did
service : {
    "demo_to_string_128": () -> () query; 
};

icpp.toml

The icpp.toml configuration file resides in the root of your project, and it defines the source files, and what compile & link flags to use.

You can select a different configuration toml file using the --config option

icpp-pro has the following build commands:

1. icpp build-wasm

  • builds a wasm to deploy to an IC canister
  • reads the [build-wasm] section of the icpp.toml
  • run icpp build-wasm --help for options
  • use icpp build-wasm --config TOML_FILE_NAME to use a different file for icpp.toml
  • use icpp build-wasm --to-compile mine-no-lib to skip building the libraries

2. icpp build-library

  • builds a static library for inclusion in the wasm
  • reads the [[build-library]] sections of the icpp.toml
  • run icpp build-library --help for options
  • use icpp build-library --config TOML_FILE_NAME to use a different file for icpp.toml

3. icpp build-native

  • builds a native debug executable, for interactive debugging in VS Code
  • reads the [build-native] section of the icpp.toml
  • run icpp build-native --help for options
  • use icpp build-native --config TOML_FILE_NAME to use a different file for icpp.toml
  • use icpp build-native --to-compile mine-no-lib to skip building the libraries

4. icpp build-library-native

  • builds a static library for inclusion in the native executable
  • reads the [[build-library-native]] sections of the icpp.toml
  • run icpp build-library-native --help for options
  • use icpp build-library-native --config TOML_FILE_NAME to use a different file for icpp.toml

The content of the icpp.toml is best explained by this annotated example.

Note that for the lists of paths & dirs, you can use python glob operators, like "scr/*.cpp"

[build-wasm]
# The name of the canister, must match definition in dfx.json
canister = "my_canister"
# The relative path to the candid file of the canister
did_path = "src/my_canister.did"
# A list of relative paths to the C++ files of the canister
cpp_paths = ["src/*.cpp", "src1/more/*.cpp"]
# A list of relative directory paths, where the compiler must look for C++ header files.
# - by default, the compiler already checks the directory where the toml file resides and the src sub-directory
cpp_include_dirs = ["src/vendors/*"]
# A list of additional C++ compile flags.
cpp_compile_flags = ["-D JSON_HAS_FILESYSTEM=0"]
# A list of additional link flags.
cpp_link_flags = []
# A list of relative paths to the C files of the canister
c_paths = []
# A list of relative directory paths, where the compiler must look for C header files.
# - by default, the compiler already checks the directory where the toml file resides and the src sub-directory
c_include_dirs = []
# A list of additional C compile flags.
c_compile_flags = []
# Seldomly used - if non-empty, this will overwrite internal default compile & link flags
cpp_compile_flags_defaults = []
cpp_link_flags_defaults = []
c_compile_flags_defaults = []
[build-native]
# A list of relative paths to the additional C++ files to include in the native debug build.
# - the native debug build uses ALL the files listed in the [build-wasm] section, plus these
# - typically, it is just a main program with calls to canister methods, using the MockIC
cpp_paths = ["native/main.cpp"]
# A list of relative directory paths, where the compiler must look for C++ header files.
# - the native debug build uses ALL the include_dirs listed in the [build-wasm] section, pluse these
cpp_include_dirs = []
# A list of additional C++ compile flags.
# - the native debug build has it's own unique flags, and is NOT using the ones listed in the [build-wasm] section
cpp_compile_flags = []
# A list of additional link flags.
# - the native debug build has it's own unique flags, and is NOT using the ones listed in the [build-wasm] section
cpp_link_flags = []
# A list of relative paths to the additional C files to include in the native debug build.
# - the native debug build uses ALL the files listed in the [build-wasm] section, plus these
c_paths = []
# A list of relative directory paths, where the compiler must look for C header files.
# - the native debug build uses ALL the include_dirs listed in the [build-wasm] section, pluse these
c_include_dirs = []
# A list of additional C compile flags.
# - the native debug build has it's own unique flags, and is NOT using the ones listed in the [build-wasm] section
c_compile_flags = []
# Seldomly used - if non-empty, this will overwrite internal default compile & link flags
cpp_compile_flags_defaults = []
cpp_link_flags_defaults = []
c_compile_flags_defaults = []

[[build-library]]
# Name of the static library
lib_name = "libhello"
# Meaning of all fields below are same as described in [build-wasm]
cpp_paths = ["libhello/*.cpp"]
cpp_include_dirs = []
cpp_compile_flags = []
c_paths = []
c_include_dirs = []
c_compile_flags = []

[[build-library]]
# Name of the static library
lib_name = "libworld"
# Meaning of all fields below are same as described in [build-wasm]
cpp_paths = ["libworld/*.cpp"]
cpp_include_dirs = []
cpp_compile_flags = []
c_paths = []
c_include_dirs = []
c_compile_flags = []


MockIC

The MockIC is a unique feature of icpp-pro. It allows you to test & interactively debug your C++ code using a native debug executable.

You use it in the following developer friendly workflow:

  • create a set of unit tests in a main program, typically native/main.cpp
  • build a native debug executable using the icpp build-native command
  • this creates the executable build-native/mockic.exe
  • debug it in VS Code, using CodeLLDB
  • you can also run it from the command line, which is recommended to do in a CI/CD pipeline

This approach accelerates development:

  • native debug builds are much faster than the optimized wasm builds
  • you can skip the deploy step
  • you can debug with VS Code, setting breakpoints and explore the data
  • you do NOT have to rely on debug_print statements, although you can use them as usual

Because the MockIC uses the exact same code for Candid decoding and encoding as when your code is running in a canister using WebAssembly, the data flow of messages coming in over the wire and going out to the wire is accurately modeled. You can build realistic test scenarios, and then debug them interactively.

We rely on this capability extensively and it increases development velocity tremendously. Being able to debug with breakpoints, explore the content of your data and step through the code as it runs is simply invaluable.

A good example how we used this capability is by looking at the native/main.cpp file for icpp_llama2, the backend canister of ICGPT. It would have been very difficult to port this LLM to the IC without native debug capability.

The way you use it is best explained by these code snipperts of the native/main.cpp file from our api-reference demo canister.

You need to pass in the serialized candid arguments as a hex string. For that, we recommend using didc.

// file: native/main.cpp

#include "main.h"

#include <iostream>

// include files declaring the canister methods we want to test
#include "../src/demo_candid_type_bool.h"
#include "../src/demo_trap.h"

// The Mock IC coming with icpp-pro
#include "mock_ic.h"

int main() {
  MockIC mockIC(true); // 'true' will debug_print

  // -----------------------------------------------------------------------------
  // Configs for the tests:

  // The pretend principal of the caller
  std::string my_principal{
      "expmt-gtxsw-inftj-ttabj-qhp5s-nozup-n3bbo-k7zvn-dg4he-knac3-lae"};

  bool silent_on_trap = true;

  // -----------------------------------------------------------------------------
  // Call a canister method and check the response:
  // Arguments to the run_test method of the mockIC:
  //   "demo_candid_type_bool" : name of the unit test
  //   demo_candid_type_bool   : the method to call:
  //                             -> must be defined in include above
  //                             -> mockIC internally uses a callback mechanism
  //   "4449444c00017e01"      : the hex string of the encoded argument.
  //   "4449444c00017e01"      : the hex string of the encoded expected result.
  //   silent_on_trap          : true  - when canister method traps, mockIC will not print the trap message
  //                             false - when canister method traps, mockIC will print the trap message
  //   my_principal            : a pretend principal of the caller
  //
  mockIC.run_test("demo_candid_type_bool", demo_candid_type_bool,
                  "4449444c00017e01", // didc encode '(true)'
                  "4449444c00017e01", // didc decode 4449444c00017e01
                  silent_on_trap, my_principal);

  // -----------------------------------------------------------------------------
  // Call a canister method and verify it traps:
  // Arguments to the run_trap_test method of the mockIC:
  //   "demo_trap"             : name of the unit test
  //   demo_trap               : the method to call:
  //                             -> must be defined in include above
  //                             -> mockIC internally uses a callback mechanism
  //   "4449444c0000"          : the hex string of the encoded argument.
  //   silent_on_trap          : true  - when canister method traps, mockIC will not print the trap message
  //                             false - when canister method traps, mockIC will print the trap message
  //   my_principal            : a pretend principal of the caller

  // Verify that a this method traps
  // '()' -> trap message
  mockIC.run_trap_test("demo_trap", demo_trap, "4449444c0000", silent_on_trap,
                       my_principal);

  // -----------------------------------------------------------------------------
  // prints a summary of the unit tests.
  // - returns 0 if all tests pass
  // - returns 1 if any tests failed
  return mockIC.test_summary();
}

Smoke Test

🚧 This documentation section is currently under construction! 🚧


Orthogonal Persistence

The IC automatically persists static/globally managed data after an update call.

see memory: Orthogonal Persistence of C++ Data Structures on the Internet Computer: A Study


Static data structures (stack)

These data structures are known at compile time and all data lives on the stack. You can just define them directly in the static/global section, and Orthogonal Persistence will work.


primitives

See counter: An Orthogonal Persistence demo for uint64_t


std::array

see memory: Orthogonal Persistence of C++ Data Structures on the Internet Computer: A Study


Dynamic data structures (heap)

These STL container keeps their metadata on the stack, while the data itself lives on the heap. You must define a pointer to the STL container in the static/global section, and use new/delete in your functions.


std::string

see memory: Orthogonal Persistence of C++ Data Structures on the Internet Computer: A Study


std::unordered_map

*see counter4me: An Orthogonal Persistence demo for std::unordered_map *

(With your principal as the key!!)


std::vector

See counters: An Orthogonal Persistence demo for std::vector


std::deque

🚧 This documentation section is currently under construction! 🚧


std::list

🚧 This documentation section is currently under construction! 🚧


std::map

🚧 This documentation section is currently under construction! 🚧


std::set

🚧 This documentation section is currently under construction! 🚧