Value Container API

#include <pvxs/data.h>
namespace pvxs { ... }

pvxs::Value is the primary data container type used with PVXS. A pvxs::Value may be obtained via the remote peer (client or server), or created locally. See ntapi or typedefapi.

pvxs::Value is a safe pointer-like object which, maybe, references a node in a tree of sub-structures and leaf fields. This tree will be referred to as a Structure as it behaves in many ways like a C ‘struct’.

For example, the following code:

Value top = TypeDef(TypeCode::Struct, {
    members::Int32("fldname"),
}).create();

top["fldname"] = 1;

fld = top["fldname"];
fld = 2;

Is analogous to the following pseudo code.

// pseudo-code
struct anon {
    int32_t fldname=0u;
};
void* top = new anon;

static_cast<anon*>(top)->fldname = 1;

void* fld = &static_cast<anon*>(top)->fldname;
static_cast<int32_t*>(fld) = 2;

With the chief functional difference being that the analogs of the casts are made safe. Also, the storage of the underlying Structure will be free’d when no more Values reference it.

A Value which does not reference any underlying Structure is not valid, or “empty”.

Value dummy;
assert(!dummy.valid());
assert(!dummy); // operator bool() is an alias for valid()

An invalid Value may be returned on error by some methods. All operations on an invalid Value should be safe and well defined.

Value top(nt::NTScalar{TypeCode::Int32}.create());
int32_t val = top["nonexistent"].as<int32_t>();

In this example, the operator[] lookup of a non-existent field returns an invalid Value. Attempting to extract an integer from this will then throw a pvxs::NoField exception.

Value

Field Lookup

Access to members of structured types is accomplished through pvxs::Value::operator[]() or pvxs::Value::lookup(). These two methods differ in how errors are communicated. operator[] will return an “invalid” or “empty” Value if the expression does not address a member. lookup() will throw an exception describing where and how expression evaluation failed.

Iteration

pvxs::Value instances pointing to a non-array structured data field (Struct or Union) may be iterated. Iteration comes in three variations: pvxs::Value::iall(), pvxs::Value::ichildren(), and pvxs::Value::imarked().

For a Struct, iall() is a depth first traversal of all fields. ichildren() traverses all child fields (excluding eg. grandchildren and further). imarked() considers all fields, but only visits those which have beem marked (pvxs::Value::isMarked()).

For a Union. iall() and ichildren() are identical, and will visit all possible Union members, excluding the implicit NULL member. Traversal does not effect member selection. imarked() for a Union will visit at most one member (if one is selected)>

Iteration of Union may return Value instances allocated with temporary storage. Changes to these instances will not effect the underlying structure.

Iteration of other field types, including StructA and UnionA is not implemented at this time, and will always appear as empty.

class Value

Generic data container

References a single data field, which may be free-standing (eg. “int x = 5;”) or a member of an enclosing Struct, or an element in an array of Struct.

  • Use valid() (or operator bool() ) to determine if pointed to a valid field.

  • Use operator[] to traverse within a Kind::Compound field.

Value val = nt::NTScalar{TypeCode::Int32}.create();
val["value"] = 42;
Value alias = val;
assert(alias["value"].as<int32_t>()==42); // 'alias' is a second reference to the same Struct

Public Functions

inline constexpr Value()

default empty Value

Value cloneEmpty() const

allocate new storage, with default values

Value clone() const

allocate new storage and copy in our values

Value &assign(const Value &o)

copy value(s) from other. Acts like from(o) for kind==Kind::Compound . Acts like from(o.as<T>()) for kind!=Kind::Compound

Value allocMember()

Use to allocate members for an array of Struct and array of Union.

void clear()

Restore to newly allocated state.

Free any allocation for array or string values, zero numeric values. unmark() all fields.

Since

1.1.0

inline bool valid() const

Does this Value actually reference some underlying storage.

bool isMarked(bool parents = true, bool children = false) const

Test if this field is marked as valid/changed.

Value ifMarked(bool parents = true, bool children = false) const

return *this if isMarked()==true, or a !valid() ref. if false.

void mark(bool v = true)

Mark this field as valid/changed.

void unmark(bool parents = false, bool children = true)

Remove mark from this field, and optionally parent and/or child fields.

Since

1.1.3 Correctly unmark parent fields

TypeCode type() const

Type of the referenced field (or Null)

StoreType storageType() const

Type of value stored in referenced field.

const std::string &id() const

Type ID string (Struct or Union only)

bool idStartsWith(const std::string &prefix) const

Test prefix of Type ID string (Struct or Union only)

inline bool equalInst(const Value &o) const

Test for instance equality. aka. this==this.

inline bool equalType(const Value &o) const

Test for equality of type only (including field names)

const std::string &nameOf(const Value &descendant) const

Return our name for a descendant field.

Value v = ...;
assert(v.nameOf(v["some.field"])=="some.field");

Throws:
  • NoField – unless both this and descendant are valid()

  • std::logic_error – if descendant is not actually a descendant

template<typename T>
inline T as() const

Extract from field.

Type ‘T’ may be one of:

  • bool

  • uint8_t, uint16_t, uint32_t, uint64_t

  • int8_t, int16_t, int32_t, int64_t

  • float, double

  • std::string

  • Value

  • shared_array<const void>

  • An enum where the underlying type is one of the preceding (since 0.2.0).

Throws:
template<typename T>
inline bool as(T &val) const

Attempt to extract value from field.

Returns:

false if as<T>() would throw NoField or NoConvert

template<typename T, typename FN>
inline impl::StorageMap<typenamestd::decay<FN>::type>::not_storable as(FN &&fn) const

Attempt to extract value from field. If possible, this value is cast to T and passed as the only argument of the provided function.

template<typename T>
inline bool tryFrom(const T &val)

Attempt to assign to field.

Returns:

false if from<T>() would throw NoField or NoConvert

template<typename T>
inline void from(const T &val)

Assign from field.

Type ‘T’ may be one of:

  • bool

  • uint8_t, uint16_t, uint32_t, uint64_t

  • int8_t, int16_t, int32_t, int64_t

  • float, double

  • std::string

  • Value

  • shared_array<const void>

  • An enum where the underlying type is one of the preceding (since 0.2.0).

template<typename T, typename K>
inline Value &update(K key, const T &val)

Inline assignment of sub-field. Shorthand for

(*this)[key].from(val) 

template<typename T>
inline Value &operator=(const T &val)

shorthand for from<T>(const T&) except for T=Value (would be ambiguous with ref. assignment)

Value operator[](const std::string &name)

Attempt to access a descendant field.

Argument may be:

  • name of a child field. eg. “value”

  • name of a descendant field. eg “alarm.severity”

  • element of an array of structures. eg “dimension[0]”

  • name of a union field. eg. “->booleanValue”

These may be composed. eg.

  • “dimension[0]size”

  • ”value->booleanValue”

Returns:

A valid() Value if the descendant field exists, otherwise an invalid Value.

Value lookup(const std::string &name)

Attempt to access a descendant field, or throw exception.

Acts like operator[] on success, but throws a (hopefully descriptive) exception instead of returning an invalid Value.

Since

1.1.2 An empty Value correctly throws NoField instead of returning an empty Value

Throws:
size_t nmembers() const

Number of child fields. only Struct, StructA, Union, UnionA return non-zero

Since

1.1.3 correctly return non-zero for StructA and UnionA

inline IAll iall() const noexcept

Depth-first iteration of all descendant fields

Value top(...);
for(auto fld : top.iall()) {
    std::cout<<top.nameOf(fld)<<" = "<<fld<<"\n";
}

inline IChildren ichildren() const noexcept

iteration of all child fields

inline IMarked imarked() const noexcept

Depth-first iteration of all marked descendant fields.

inline Fmt format() const

Configurable printing via std::ostream

Value val;
std::cout<<val.format().arrayLimit(10);

struct _IAll
struct _IChildren
struct _IMarked
struct Fmt

Provides options to control printing of a Value via std::ostream.

Public Functions

inline Fmt &tree()

Show Value in tree/struct format.

inline Fmt &delta()

Show Value in delta format.

inline Fmt &format(format_t f)

Explicitly select format_t.

inline Fmt &showValue(bool v)

Whether to show field values, or only type information.

inline Fmt &arrayLimit(size_t cnt)

When non-zero, arrays output will be truncated with “…” after cnt elements.

template<typename T>
struct Iterable
struct NoField : public std::runtime_error

Thrown when accessing a Null Value.

struct NoConvert : public std::runtime_error

Thrown when a Value can not be converted to the requested type.

struct LookupError : public std::runtime_error

Array fields

Array fields are represented with the pvxs::shared_array container using void vs. non-void, and const vs. non-const element types.

Arrays are initially created as non-const and non-void. After being populated, an array must be transformed using pvxs::shared_array::freeze() to become const before being stored in a pvxs::Value.

shared_array<double> arr({1.0, 2.0});
Value top = nt::NTScalar{TypeCode::Float64A}.create();

top["value"] = arr.freeze();
# freeze() acts like std::move().  arr is now empty
# only the read-only reference remains!

The pvxs::shared_array::freeze() method is special in that it acts like std::move() in that it moves the array reference into the returned object. freeze() requires exclusive ownership of the reference being frozen. An exception will be thrown unless pvxs::shared_array::unique() would return true.

Array values may be extracted from pvxs::Value as either const void or const non-void. The const non-void option is a convenience which may allocate and do an element by element conversion.

# extract reference, or converted copy
arr = top["value"].as<shared_array<const double>>();

When it is desirable to avoid an implicit allocate and convert, an array can be extracted as “const void”. This does require calling pvxs::shared_array::original_type() to find the pvxs::ArrayType of the underlying array prior to using pvxs::shared_array::castTo().

# extract untyped reference.  Never copies
shared_array<const void> varr = top["value"].as<shared_array<const void>>();
if(varr.original_type()==ArrayType::Float64) {
    # castTo() throws std::logic_error if the underlying type is not 'double'.
    shared_array<const double> temp = varr.castTo<const double>();
}
template<typename E, class Enable>
class shared_array : public pvxs::detail::sa_base<E>

std::vector-like contiguous array of items passed by reference.

shared_array comes in const and non-const, as well as void and non-void variants.

A non-const array is allocated and filled, then last non-const reference is exchanged for new const reference. This const reference can then be safely shared between various threads.

shared_array<uint32_t> arr({1, 2, 3});
assert(arr.size()==3);
shared_ptr<const uint32_t> constarr(arr.freeze());
assert(arr.size()==0);
assert(constarr.size()==3);

The void / non-void variants allow arrays to be moved without explicit typing. However, the void variant preserves the original ArrayType.

shared_array<uint32_t> arr({1, 2, 3});
assert(arr.size()==3);
shared_array<void> voidarr(arr.castTo<void>());
assert(arr.size()==0);
assert(voidarr.size()==3); // void size() in elements

Public Functions

template<typename A>
inline shared_array(std::initializer_list<A> L)

allocate new array and populate from initializer list

template<typename Iter, typename std::iterator_traits<Iter>::difference_type = 0>
inline shared_array(Iter begin, Iter end)

Construct a copy of another a sequence. Requires random access iterators.

inline explicit shared_array(size_t c)

Allocate (with new[]) a new vector of size c.

template<typename V>
inline shared_array(size_t c, V e)

Allocate (with new[]) a new vector of size c and fill with value e.

inline shared_array(E *a, size_t len)

use existing alloc with delete[]

template<typename B>
inline shared_array(E *a, B b, size_t len)

use existing alloc w/ custom deletor

inline shared_array(const std::shared_ptr<E> &a, size_t len)

build around existing shared_ptr

template<typename A>
inline shared_array(const std::shared_ptr<A> &a, E *b, size_t len)

alias existing shared_array

size_t size() const

Number of elements.

bool empty() const

size()==0

bool unique() const

True if this instance is the only (strong) reference.

void clear()

Reset size()==0.

void swap(shared_array &o)

Exchange contents with other.

E *data() const noexcept

Access to raw pointer. May be nullptr if size()==0

inline void resize(size_t i)

Extend size. Implies make_unique().

Post:

unique()==true

inline void make_unique()

Ensure exclusive ownership of array data by making a copy if necessary.

Post:

unique()==true

inline iterator begin() const noexcept

begin iteration

inline iterator end() const noexcept

end iteration

inline reference operator[](size_t i) const noexcept

Member access Use sa.data() instead of &sa[0].

Pre:

!empty() && i<size()

inline reference at(size_t i) const

Member access.

Throws:

std::out_of_range – if empty() || i>=size().

inline shared_array<typename std::add_const<E>::type> freeze()

Cast to const, consuming this

Throws:

std::logic_error – if !unique()

Pre:

unique()==true

Post:

empty()==true

inline shared_array<typename std::remove_const<E>::type> thaw()

Return non-const (maybe) copy. consuming this

If

unique(), transforms this reference into the returned const reference. If not unique(), returns a copy and clears this reference. In either case, the returned reference will be unique().
Since

1.1.2

Post:

empty()==true

template<typename TO>
shared_array<TO> castTo() const

Cast to/from void, preserving const-ness.

A “safe” version of static_cast<>()

Allowed casts depend upon two aspects of type parameter E.

Whether the base type is void or non-void. And whether or not the const qualifier is present.

Type E may always be cast to itself.

Casts must preserve const-ness. Either both of E and TO, or neither, must be const qualified.

At most one of E or TO may have different non-void base type.

Throws:

std::logic_error – on void -> non-void cast when requested type and the original_type() do not match.

template<typename TO>
shared_array<TO> convertTo() const

Cast with fallback to copy. Preserves const-ness

Return either a reference or a copy of this array. A copy will be made if the requested type and the original_type() do not match. Otherwise functions like castTo().

inline detail::Limiter format() const

Provide options when rendering with std::ostream.

shared_array<int32_t> arr({1,2,3,4});
// print entire array
//   {4}[1,2,3,4]
std::cout<<arr;
// print at most 3 elements
//   {4}[1,2,3,...]
std::cout<<arr.format().limit(3);

inline ArrayType original_type() const

return type of underlying array. (void only)

size_t pvxs::elementSize(ArrayType type)

Return storage size (aka. sizeof() ) for array element type

Throws:

std::logic_error – for invalid types.

class Limiter

Provide options when rendering with std::ostream.

Public Functions

inline Limiter &limit(size_t l)

Maximum number of array elements to print. “…” is printed in place of any further elements.

enum class pvxs::ArrayType : uint8_t

Identify real array type in void specializations of shared_array.

Values:

enumerator Null

Untyped.

enumerator Bool

bool

enumerator Int8

int8_t

enumerator Int16

int16_t

enumerator Int32

int32_t

enumerator Int64

int64_t

enumerator UInt8

uint8_t

enumerator UInt16

uint16_t

enumerator UInt32

uint32_t

enumerator UInt64

uint64_t

enumerator Float32

float

enumerator Float64

double

enumerator String

std::string

enumerator Value

Value.