Files
toml11/docs/content.en/docs/features/value.md
2024-06-15 19:27:42 +09:00

800 lines
22 KiB
Markdown

+++
title = "getting values"
type = "docs"
weight = 20
+++
# Retrieving Values
This section explains how to access the values stored in `toml::value`.
## Accessing Values Using Member Functions
### `is_something` and `as_something`
`toml::value` has member functions like `is_boolean()` and `is_integer()` which allow you to check the type of the stored value.
Additionally, it has member functions like `as_boolean()` and `as_integer()` that allow you to access the value of that type.
For a complete list, refer to the [`toml::value` reference]({{<ref "docs/reference/value#is_xxx">}}).
```cpp
toml::value v = /* ... */;
if(v.is_string())
{
std::cout << v.as_string() << std::endl;
}
```
If the stored value is of a different type than specified, a [`toml::type_error`]({{<ref "docs/reference/value#tomltype_error">}}) is thrown.
The `what()` method will contain a message like the following:
```
[error] toml::value::as_string(): bad_cast to string
--> input.toml
|
1 | a = 123_456
| ^^^^^^^-- the actual type is integer
```
### `toml::value_t`
Type information can be identified using [`enum class toml::value_t`]({{<ref "docs/reference/value_t">}}).
The [`type()`]({{<ref "docs/reference/value#type">}}) member function returns the type information of the currently stored value.
```cpp
toml::value v = /* ... */;
switch(v.type())
{
case toml:value_t::empty : { /*...*/ break; }
case toml:value_t::boolean : { /*...*/ break; }
case toml:value_t::integer : { /*...*/ break; }
case toml:value_t::floating : { /*...*/ break; }
case toml:value_t::string : { /*...*/ break; }
case toml:value_t::offset_datetime: { /*...*/ break; }
case toml:value_t::local_datetime : { /*...*/ break; }
case toml:value_t::local_date : { /*...*/ break; }
case toml:value_t::local_time : { /*...*/ break; }
case toml:value_t::array : { /*...*/ break; }
case toml:value_t::table : { /*...*/ break; }
default: {break;}
}
```
The [`is(toml::value_t)`]({{<ref "docs/reference/value#istomlvalue_t">}}) member function returns `true` if the stored value is of the given `value_t` type, otherwise it returns `false`.
```cpp
toml::value v = /* ... */;
if(v.is(toml::value_t::integer))
{
std::cout << v.as_integer() << std::endl;
}
```
### `at`, `[]`, `contains`, `size`, `push_back`, `emplace_back`
`toml::value` provides some member functions similar to those of standard library containers.
These functions internally convert `toml::value` to the corresponding type and call the respective member functions.
#### `at(std::size_t i)`, `operator[](std::size_t i)`
These are equivalent to `as_array().at(i)` and `as_array()[i]`.
`toml::value` uses `std::vector<toml::value>` as `array_type` by default, so `at` throws `std::out_of_range` on error, while `operator[]` results in undefined behavior.
```cpp
toml::value v(toml::array{1,2,3});
std::cout << v.at(1);
```
If the stored type is not `array_type`, a `type_error` is thrown.
#### `at(std::string key)`, `operator[](std::string key)`
These are equivalent to `as_table().at(key)` and `as_table()[key]`.
`toml::value` uses `std::unordered_map<std::string, toml::value>` as `table_type` by default, so if the corresponding value does not exist, `at` throws `std::out_of_range`, while `operator[]` constructs a new `toml::value` and returns a reference to it. Therefore, there is no `const` version of `operator[]`.
```cpp
toml::value v(toml::table{});
v["a"] = 42;
```
If the stored type is not `table_type`, a `type_error` is thrown.
#### `size()`
Returns the length.
For `array_type` or `table_type`, it returns the number of elements; for `string_type`, it returns the number of characters (in bytes).
If the stored type is none of these, a `type_error` is thrown.
#### `push_back()`, `emplace_back()`
These are equivalent to `as_array().push_back()` and `as_array().emplace_back()`.
If the stored type is not `array_type`, a `type_error` is thrown.
## Accessing Comments
In toml11, comments are parsed by default and saved line by line with the corresponding value.
A comment corresponds to the value that comes immediately after the consecutive lines of comments, or to the value on the same line as the comment.
If there is no value immediately before or after the comment, it is not associated with any value and is ignored.
```toml
# input.toml
# This is a comment about a.
a = 42
b = 3.14 # This is a comment about b.
# This comment is ignored because it has no corresponding value.
# This is the 1st comment about c.
# This is the 2nd comment about c.
c = "foo" # This is the final comment about c.
# This comment is NOT a comment about c.
```
Comments corresponding to a value can be accessed using the `comments()` member function of `toml::value`.
`comments()` returns a container that has the same member functions as `std::vector<std::string>`.
```cpp
const auto v = toml::parse("input.toml");
const auto& a = v.at("a");
const auto& b = v.at("b");
const auto& c = v.at("c");
assert(a.comments().size() == 1);
assert(a.comments().at(0) == "# This is a comment about a.");
assert(b.comments().size() == 1);
assert(b.comments().at(0) == "# This is a comment about b.");
assert(c.comments().size() == 3);
assert(c.comments().at(0) == "# This is the 1st comment about c.");
assert(c.comments().at(1) == "# This is the 2nd comment about c.");
assert(c.comments().at(2) == "# This is the final comment about c.");
```
Comments related to the root table of the entire file are written at the beginning of the file.
```toml
# This is a comment about the root table.
# This is also a comment about the root table.
# This comment is ignored.
# This is a comment about a.
a = 42
```
However, if a value immediately follows the initial comment, the comment is interpreted as pertaining to that value, and there are no comments for the root table.
```toml
# This is a comment about a.
# This is also a comment about a.
a = 42
```
## Handling Inline Tables and Dotted Keys
An inline table is simply a table, and there is no difference in handling it compared to other tables in C++ code.
```toml
a = {b = 42, c = "foo"}
```
Dotted keys are also just tables, with no difference in handling compared to other tables in C++ code.
```toml
a.b = 42
a.c = "foo"
```
These TOML files are identical to the following file.
```toml
[a]
b = 42
c = "foo"
```
Thus, they can all be processed with the exact same code.
```cpp
const auto input = toml::parse("input.toml");
assert(input.at("a").at("b").as_integer() == 42);
assert(input.at("a").at("c").as_string() == "foo");
```
However, it is possible to distinguish them based on format information.
```cpp
const auto input = toml::parse("input.toml");
switch(input.at("a").as_table_fmt().fmt)
{
case toml::table_format::oneline:
{
std::cout << "inline table" << std::endl;
break;
}
case toml::table_format::multiline:
{
std::cout << "normal table" << std::endl;
break;
}
case toml::table_format::dotted:
{
std::cout << "dotted keys" << std::endl;
break;
}
}
```
This format information is also considered during serialization, which will be described later.
## Handling Date and Time Information
[`local_date`]({{<ref "docs/reference/datetime#local_date">}}),
[`local_time`]({{<ref "docs/reference/datetime#local_time">}}),
[`local_datetime`]({{<ref "docs/reference/datetime#local_datetime">}}), and
[`offset_datetime`]({{<ref "docs/reference/datetime#offset_datetime">}})
are parsed into dedicated structures with corresponding member variables in toml11.
When using these, you can directly extract the values or use `toml::get` and `toml::find` to convert them into types such as `std::chrono::system_clock::time_point` or `std::tm`.
## Converting Using `toml::get<T>`
`toml::get<T>` is a function that converts and retrieves the value stored in `toml::value`.
Specify the desired target type as `T`.
```cpp
const toml::value v = /*...*/;
std::cout << toml::get<int>(v) << std::endl;
```
The `toml::find<T>` function, described later, also has the same functionality for type conversion.
If an unsupported type is specified for the stored value, a `toml::type_error` is thrown.
### Simple Conversions
#### boolean_type
Conversion from `boolean_type` is possible only to `bool`.
#### integer_type
Any type for which `std::is_integral<T>` is `true`, except `bool`, can be converted from `integer_type`.
```cpp
toml::value v(42);
const auto u32 = toml::get<std::uint32_t>(v);
const auto i16 = toml::get<short>(v);
```
#### floating_type
Any type for which `std::is_floating_point<T>` is `true` can be converted from `floating_type`.
```cpp
toml::value v(3.14);
const auto f64 = toml::get<double>(v);
const auto f32 = toml::get<float >(v);
```
#### string_type
`string_type` can be converted to `std::string`.
From C++17 onwards, it can also be converted to `std::string_view`.
```cpp
toml::value v("foo");
const auto s = toml::get<std::string>(v);
// C++17
const auto sv = toml::get<std::string_view>(v);
```
#### datetime variants
[`local_date`]({{<ref "docs/reference/datetime#local_date">}}),
[`local_datetime`]({{<ref "docs/reference/datetime#local_datetime">}}), and
[`offset_datetime`]({{<ref "docs/reference/datetime#offset_datetime">}}) represent specific dates and times,
so they can be converted to `std::chrono::system_clock::time_point`.
However, since [`local_time`]({{<ref "docs/reference/datetime#local_time">}}) does not include date information, it supports conversion to `std::chrono::duration` as the elapsed time from `00:00.00`.
Additionally, `local_date` and `local_datetime` conversions take the executing machine's timezone into account.
```toml
date = 2024-01-23
time = 12:30:00
l_dt = 2024-01-23T12:30:00
o_dt = 2024-01-23T12:30:00+09:00
```
```cpp
const auto input = toml::parse("input.toml");
const auto date = toml::get<std::chrono::system_clock::time_point>(input.at("date"));
const auto l_dt = toml::get<std::chrono::system_clock::time_point>(input.at("l_dt"));
const auto o_dt = toml::get<std::chrono::system_clock::time_point>(input.at("o_dt"));
const auto time = toml::get<std::chrono::minutes>(input.at("time")); // 12 * 60 + 30 min
```
### Conditions for Obtaining References
`toml::get<T>` can return a reference if `T` is the exact type stored in `toml::value`.
Conversely, if a conversion is necessary (e.g., extracting an integer stored as `std::int64_t` into `std::uint32_t`), it is not possible to return a reference to the converted type.
When no conversion is needed, the returned reference can be used to modify the value.
```cpp
toml::value v(42);
toml::get<toml::value::integer_type>(v) = 6 * 9;
assert(v.as_integer() == 54);
```
### Converting Arrays to STL Containers
If all elements in an array have the same type and can be converted to `T`, it is possible to convert them to `std::vector<T>`.
```toml
a = [1, 2, 3, 4, 5]
```
```cpp
const auto a = toml::get<std::vector<int>>(input.at("a"));
```
Other STL containers can also be used.
```cpp
const auto a1 = toml::get<std::deque<int>>(input.at("a"));
const auto a2 = toml::get<std::list <int>>(input.at("a"));
const auto a3 = toml::get<std::array<int, 5>>(input.at("a"));
```
When converting to `std::array`, the number of elements must match. If they don't match, a `std::out_of_range` exception is thrown.
Non-STL containers that have a default constructor and a `push_back` method can also be converted using `toml::get`.
```cpp
const auto a = toml::get<boost::container::small_vector<int, 8>>(input.at("a"));
```
### Converting Arrays to `std::pair` or `std::tuple`
If an array contains elements of different types, it can be converted to `std::pair` or `std::tuple`.
```toml
a = [true, 3.14]
b = [42, 2.718, "foo"]
```
```cpp
const auto a = toml::get<std::pair<bool, double>>(input.at("a"));
const auto b = toml::get<std::tuple<int, double, std::string>>(input.at("b"));
```
As with `std::array`, the length of the array must match the number of elements in the `std::pair` or `std::tuple`. If they don't match, a `std::out_of_range` exception is thrown.
Additionally, each element must be convertible to the corresponding element in the `std::pair` or `std::tuple`. If conversion is not possible, a `toml::type_error` exception is thrown.
### Converting Nested Arrays
Nested arrays can be converted to nested containers.
```toml
a = [ [1, 2, 3], [4, 5, 6] ]
```
```cpp
const auto a = toml::get<std::vector<std::vector<int>>>(input.at("a"));
```
If the types are different, `std::pair` or `std::tuple` can be useful.
```toml
a = [ [1, 2, 3], ["foo", "bar"] ]
```
```cpp
const auto a = toml::get<
std::pair<std::vector<int>, std::vector<std::string>>
>(input.at("a"));
```
### Converting Tables to `std::map`
If all values in a table have the same type, they can be converted to `std::map` or `std::unordered_map`.
```toml
t = {a = 1, b = 2}
```
```cpp
const auto t = toml::get<std::map<std::string, int>>(input.at("t"));
```
Non-STL containers that have a default constructor and an `emplace(key, mapped)` method can also be converted using `toml::get`.
```cpp
const auto t = toml::get<boost::container::flat_map<std::string, int>>(input.at("t"));
```
If the conversion of any element fails, a `toml::type_error` exception is thrown.
## Using `toml::get_or` to Specify a Value on Failure
`toml::get` throws a `toml::type_error` exception if the conversion fails.
By using `toml::get_or`, you can specify a default value to return instead of an exception in case of a conversion failure.
Unlike `toml::get<T>`, `get_or` infers the target type from the arguments, so there is no need to specify `<T>`.
```cpp
const auto a = toml::get_or(input.at("a"), 42);
```
The types that can be converted are the same as for `toml::get`.
If you specify `toml::value::xxx_type`, you can also retrieve a reference, but in that case, the argument must also be a reference.
```cpp
toml::value::integer_type a_default = 42;
auto a& = toml::get_or(input.at("a"), a_default);
```
## Using `toml::find<T>` to Search and Convert Simultaneously
`toml::find<T>` is a function that searches for a value in a `toml::value` holding a table and simultaneously performs the same type conversion as `toml::get`.
```cpp
const auto a = toml::find<int>(input, "a");
// Equivalent to: const auto a = toml::get<int>(input.at("a"));
```
`toml::find<T>` can also be used with arrays.
```cpp
const auto a = input.at("a");
const auto a2 = toml::find<int>(a, 2);
// Equivalent to: const auto a2 = toml::get<int>(input.at("a").at(2));
```
If an error occurs during type conversion, `toml::find<T>` throws the same `toml::type_error` as `toml::get`.
If the key is not found or the index does not exist, it throws `std::out_of_range`.
If no type is specified, `toml::find` returns a `toml::value` without type conversion.
```cpp
const auto a = toml::find(input, "a");
// Equivalent to: const auto a = input.at("a");
```
`toml::find<T>` can access values recursively.
```toml
a = {b = {c = 42}}
```
```cpp
const auto a_b_c = toml::find<int>(input, "a", "b", "c");
// Equivalent to: const auto a = toml::get<int>(input.at("a").at("b").at("c"));
```
You can mix keys and indexes.
```toml
a = [ {b = 1}, {b = 2}, {b = 3} ]
```
```cpp
const auto a_2_b = toml::find<int>(input, "a", 2, "b");
// Equivalent to: const auto a = toml::get<int>(input.at("a").at(2).at("c"));
```
{{<hint info>}}
TOML supports a feature called quoted keys, which allows using normally disallowed characters in keys by enclosing them in `""` or `''`. Within quoted keys, `.` does **not** introduce tables.
```toml
"127.0.0.1" = "value"
site."google.com" = true
```
You can read this TOML file as follows:
```cpp
const auto input = toml::parse("input.toml");
assert(input.at("127.0.0.1").as_string() == "value");
assert(input.at("site").at("google.com").as_boolean());
```
To handle such cases seamlessly, toml11 does not automatically split keys containing `.`. Explicitly specifying the table hierarchy helps in structuring the input file correctly.
Reference: [toml.io/Key](https://toml.io/en/v1.0.0#key)
{{</hint>}}
## Using `toml::find_or` to Search and Specify a Value on Failure
`toml::find_or` works similarly to `toml::find<T>`, but allows specifying a default value to return if the search or conversion fails.
This is useful when you want to provide a fallback value instead of handling exceptions.
```cpp
const auto a = toml::find_or(input, "a", 42);
```
## Defining Conversions for User-Defined Types
With `toml::get` and `toml::find`, you can use user-defined types by employing one of the following methods.
### Defining `toml::from`
In toml11, there is a `toml::from` type that supports conversions from user-defined types by specializing it as follows:
```cpp
namespace extlib
{
struct foo
{
int a;
std::string b;
};
} // extlib
namespace toml
{
template<>
struct from<extlib::foo>
{
static extlib::foo from_toml(const toml::value& v)
{
return extlib::foo{
toml::find<int>(v, "a"),
toml::find<std::string>(v, "b")
};
}
};
} // toml
```
If you also want to support `toml::value` with a modified type configuration, do as follows:
```cpp
namespace extlib
{
struct foo
{
int a;
std::string b;
};
} // extlib
namespace toml
{
template<>
struct from<extlib::foo>
{
template<typename <TC>
static extlib::foo from_toml(const toml::basic_value<TC>& v)
{
return extlib::foo{
toml::find<int>(v, "a"),
toml::find<std::string>(v, "b")
};
}
};
} // toml
```
This definition can be automatically generated using `TOML11_DEFINE_CONVERSION_NON_INTRUSIVE`.
```cpp
namespace extlib
{
struct foo
{
int a;
std::string b;
};
} // extlib
TOML11_DEFINE_CONVERSION_NON_INTRUSIVE(extlib::foo, a, b)
```
Alternatively, you can use a reflection library. Refer to the sample in `example` using `boost-ext/reflect`.
### Defining a `from_toml` Member Function
You can also define a conversion by defining a `from_toml` member function.
When using this method, a default constructor is required.
```cpp
struct bar
{
int a;
std::string b;
void from_toml(const toml::value& v)
{
this->a = toml::find<int>(v, "a");
this->b = toml::find<std::string>(v, "b");
return ;
}
};
```
If both are defined, `toml::from` takes precedence.
### Constructor Accepting `toml::value`
If there is a constructor that accepts `toml::value`, conversion via `toml::get` can be performed.
```cpp
struct baz
{
explicit baz(const toml::value& v)
: a(toml::find<int>(v, "a")), b(toml::find<std::string>(v, "b"))
{}
int a;
std::string b;
};
```
If both are defined, `toml::from` and `from_toml` take precedence.
## Applying Functions with `toml::visit`
If you have a function object that can be applied to all types stored in `toml::value`, you can directly call that function without type conversion using `toml::visit`.
```cpp
struct type_name_of
{
std::string operator()(const toml::value::boolean_type &) const {return "boolean";}
std::string operator()(const toml::value::integer_type &) const {return "integer";}
std::string operator()(const toml::value::floating_type &) const {return "floating";}
std::string operator()(const toml::value::string_type &) const {return "string";}
std::string operator()(const toml::value::local_time_type &) const {return "local_time";}
std::string operator()(const toml::value::local_date_type &) const {return "local_date";}
std::string operator()(const toml::value::local_datetime_type &) const {return "local_datetime";}
std::string operator()(const toml::value::offset_datetime_type&) const {return "offset_datetime";}
std::string operator()(const toml::value::array_type &) const {return "array";}
std::string operator()(const toml::value::table_type &) const {return "table";}
};
toml::value v(3.14);
std::cout << toml::visit(type_name_of{}, v) << std::endl; // floating
```
## Constructing `toml::value`
`toml::value` can be constructed not only internally by the parser but also in user code.
You can construct it by passing a type that is either the same as or convertible to the types stored in `toml::value`.
```cpp
toml::value v1(true);
toml::value v2(42);
toml::value v3(3.14);
```
For arrays, you can use `toml::array`:
```cpp
toml::value v(toml::array{1, 2, 3});
```
Alternatively, you can pass containers such as `std::vector` directly:
```cpp
const std::vector<toml::value> a{1,2,3};
toml::value v(a);
```
For tables, you can use `toml::table`:
```cpp
toml::value v(toml::table{{"foo", 1}, {"bar", 2}, {"baz", 3}});
```
Or pass containers such as `std::map` directly:
```cpp
const std::map<std::string, toml::value> t{
{"foo", 1}, {"bar", 2}, {"baz", 3}
}
toml::value v(t);
```
You can pass `format_info` and comments to the constructor.
The type of comments is `std::vector<std::string>`.
Each element corresponds to a line.
```cpp
toml::integer_format_info fmt;
fmt.fmt = toml::integer_format::hex;
fmt.spacer = 4;
toml::value v1(0xDEADBEEF, fmt);
toml::value v2(0xC0FFEE, fmt, {"hex value!"});
```
## Converting to `toml::value`
When constructing `toml::value` from user-defined types, you can customize the behavior by defining `toml::into` or `into_toml`.
`toml::into` is particularly useful when converting types from other libraries.
### Defining `toml::into`
By specializing `toml::into`, you can enable conversions to `toml::value`.
This is useful for types from external libraries that do not provide a conversion to `toml::value`.
Since `toml::value` passes `type_config` during conversion, you need to accept the `template` argument of `basic_value`.
```cpp
namespace extlib
{
struct foo
{
int a;
std::string b;
};
} // extlib
template<>
struct into<extlib::foo>
{
template<typename TC>
static toml::basic_value<TC> into_toml(const extlib::foo& f)
{
return toml::basic_value<TC>(typename toml::basic_value<TC>::table_type{{"a", f.a}, {"b", f.b}});
}
};
```
### Defining `into_toml` Member Function
Similar to `from_toml`, you can define the conversion through a member function.
If `toml::into` is defined, it takes precedence.
```cpp
struct bar
{
int a;
std::string b;
template<typename TC>
toml::basic_value<TC> into_toml() const
{
return toml::basic_value<TC>(typename toml::basic_value<TC>::table_type{
{"a", this->a}, {"b", this->b}
});
}
};
```