getting values

値を取り出す #

ここでは、 toml::value が格納している値にアクセスする方法を説明します。

メンバ関数を使って値にアクセスする #

is_somethingas_something #

toml::valueis_boolean()is_integer() などのメンバ関数を持っており、 これらを使うと持っている型を調べることができます。

また、 as_boolean(), as_integer() などのメンバ関数も持っており、 これらを使ってその型にアクセスすることができます。

完全なリストは toml::value のリファレンス を参照してください。

toml::value v = /* ... */;
if(v.is_integer())
{
    std::cout << v.as_integer() << std::endl;
}

指定された値と異なる型が格納されていた場合、 toml::type_error が送出されます。

その what() は以下のようなメッセージを含みます。

[error] toml::value::as_string(): bad_cast to string
 --> input.toml
   |
 1 | a = 123_456
   |     ^^^^^^^-- the actual type is integer

toml::value_t #

型情報は enum class toml::value_t で識別できます。

type() メンバ関数は、現時点で格納している値の型情報を返します。

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;}
}

is(toml::value_t) メンバ関数は、渡された value_t と同じ型の値を格納している場合 true を、 それ以外の場合 false を返します。

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 も提供しています。

これらは、内部で toml::value を対応する型に変換し、そのメンバ関数を呼び出します。

at(std::size_t i), operator[](std::size_t i) #

as_array().at(i), as_array()[i] と同等です。

toml::value はデフォルトで std::vector<toml::value>array_type に使うので、 エラーが発生した際には atstd::out_of_range を送出し、operator[] は未定義動作となります。

toml::value v(toml::array{1,2,3});
std::cout << v.at(1);

格納している型が array_type ではなかった場合、 type_error を送出します。

at(std::string key), operator[](std::string key) #

as_table().at(key), as_table()[key] と同等です。

toml::value はデフォルトで std::unordered_map<std::string, toml::value>table_type に使うので、 対応する値が存在しない場合は atstd::out_of_range を送出し、 operator[] は新しく toml::value を構築してそれへの参照を返します。 そのため、operator[]const 版はありません。

toml::value v(toml::table{});
v["a"] = 42;

格納している型が table_type ではなかった場合、 type_error を送出します。

size() #

長さを返します。

array_type または table_type の場合は要素数、 string_type の場合は文字数を返します。

格納している型がどれでもなかった場合、 type_error を送出します。

push_back(), emplace_back() #

as_array().push_back(), as_array().emplace_back() と同一です。

格納している型が array_type ではなかった場合、 type_error を送出します。

コメントにアクセスする #

toml11では、デフォルトでコメントがパースされ、対応する値に行ごとに保存されます。

対応する値は、連続するコメント行の直後に来る値か、もしくはそのコメントと同じ行に描かれている値です。

直前または直後に値がなければ、そのコメントはどこにも紐づけられず、無視されます。

# input.toml

# これはaに関するコメントです。
a = 42
b = 3.14 # これはbに関するコメントです。

# このコメントには対応する値がないため無視されます。

# これは1番目のcに関するコメントです。
# これは2番目のcに関するコメントです。
c = "foo" # これは最後のcに関するコメントです。

値に対応するコメントには、toml::valuecomments() メンバ関数を使ってアクセスします。

comments()std::vector<std::string> と同じメンバ関数を持つコンテナを返します。

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) == "# これはaに関するコメントです。");

assert(b.comments().size() == 1);
assert(b.comments().at(0) == "# これはbに関するコメントです。");

assert(c.comments().size() == 3);
assert(c.comments().at(0) == "# これは1番目のcに関するコメントです。");
assert(c.comments().at(1) == "# これは2番目のcに関するコメントです。");
assert(c.comments().at(2) == "# これは最後のcに関するコメントです。");

ファイル全体に対応するルートテーブルに関するコメントは、ファイルの先頭に書きます。

# ルートテーブルに関するコメントです。
# これもルートテーブルに関するコメントです。

# これはaに関するコメントです。
a = 42

ただし、もしファイルの先頭のコメントの直後に値が来た場合、 そのコメントはその値に関するコメントと解釈され、ルートテーブルのコメントはなくなります。

# これはaに関するコメントです。
# これもaに関するコメントです。
a = 42

インラインテーブル・ドットキーの取り扱い #

インラインテーブルは単にテーブルで、C++コード上で他のテーブルと異なる点はありません。

a = {b = 42, c = "foo"}

ドットキーも単にテーブルで、C++コード上で他のテーブルと異なる点はありません。

a.b = 42
a.c = "foo"

これらは以下のファイルと全く同じ構造を持ちます。

[a]
b = 42
c = "foo"

なので、どの記法でも以下の全く同一のコードで処理できます。

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");

ただし、フォーマット情報によって区別することは可能です。

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;
    }
}

このフォーマット情報は後述するシリアライズの際も考慮されます。

日付情報の取り扱い #

local_date, local_time, local_datetime, そして offset_datetime は、 toml11では対応するメンバ変数を持つ専用の構造体にパースされます。

使用する際は、直接値を取り出す他にも、後述する toml::gettoml::find を使用して、 std::chrono::system_clock::time_pointstd::tm 等の型に変換することができます。

toml::get<T>を使って変換する #

toml::get<T> は、 toml::value の持つ値を変換して取り出す関数です。 T に変換先に指定したい型を指定します。

const toml::value v = /*...*/;
std::cout << toml::get<int>(v) << std::endl;

後述する toml::find<T> も、型変換の部分は同一の機能を持ちます。

格納されている型のそれぞれについて、 変換ができない型が指定された場合、 toml::type_error が送出されます。

単純な変換 #

boolean_type #

boolean_type から変換が可能なのは、 bool のみです。

integer_type #

bool 以外で std::is_integral<T>true になる型は、 integer_type から変換できます。

toml::value v(42);
const auto u32 = toml::get<std::uint32_t>(v);
const auto i16 = toml::get<short>(v);

floating_type #

std::is_floating_point<T>true になる型は、floating_type から変換できます。

toml::value v(3.14);
const auto f64 = toml::get<double>(v);
const auto f32 = toml::get<float >(v);

string_type #

string_type からは std::string へ変換できます。 また、C++17以降では、std::string_view へも変換できます。

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, local_datetime, offset_datetime は ある日付と時刻を指しているため、 std::chrono::system_clock::time_point への変換が可能です。

ただし、local_time は 日付の情報がないため、0時0分からの経過時刻として std::chrono::duration への 変換をサポートします。

また、 local_datelocal_datetime は実行中のマシンのタイムゾーンを取得して変換を行います。

date = 2024-01-23
time = 12:30:00
l_dt = 2024-01-23T12:30:00
o_dt = 2024-01-23T12:30:00+09:00
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

参照を取得できる条件 #

toml::get<T> は、 Ttoml::value が格納する型そのものだった場合、参照を返すことができます。

逆に、変換が必要な場合( std::int64_t で格納されている整数を std::uint32_t で取り出そうとした場合)は、 変換後の型への参照を返すことは不可能です。

変換が必要ない型の場合、返された参照を経由して値を書き換えることも可能です。

toml::value v(42);

toml::get<toml::value::integer_type>(v) = 6 * 9;

assert(v.as_integer() == 54);

配列をSTLコンテナに #

配列の要素型が全て同じ場合、要素型が T に変換可能であれば、 std::vector<T> に変換可能です。

a = [1, 2, 3, 4, 5]
const auto a = toml::get<std::vector<int>>(input.at("a"));

他のSTLコンテナにも変換可能です。

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"));

std::array に変換する場合、要素数が一致している必要があります。 もし要素数が一致しなかった場合、 std::out_of_range が送出されます。

STL以外のコンテナであっても、デフォルトコンストラクタと push_back を持っている場合、 toml::get で変換が可能です。

const auto a = toml::get<boost::container::small_vector<int, 8>>(input.at("a"));

配列を std::pair, std::tuple#

配列の要素型が異なる場合、 std::pairstd::tuple に変換が可能です。

a = [true, 3.14]
b = [42, 2.718, "foo"]
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"));

std::array の場合と同様に、配列の長さは std::pair, std::tuple の要素数と一致している必要があります。 もし要素数が一致しなかった場合、 std::out_of_range が送出されます。

また、各要素は対応する要素に変換できる必要があります。 変換できない場合、 toml::type_error が送出されます。

ネストされた配列の変換 #

ネストされた配列は、ネストされたコンテナに変換可能です。

a = [ [1, 2, 3], [4, 5, 6] ]
const auto a = toml::get<std::vector<std::vector<int>>>(input.at("a"));

型が異なる場合には、 std::pair/tuple が便利です。

a = [ [1, 2, 3], ["foo", "bar"] ]
const auto a = toml::get<
    std::pair<std::vector<int>, std::vector<std::string>>
    >(input.at("a"));

テーブルを std::map に変換 #

テーブルに含まれる値の型が全て同じであれば、 std::mapstd::unordered_map に変換が可能です。

t = {a = 1, b = 2}
const auto t = toml::get<std::map<std::string, int>>(input.at("t"));

STL以外のコンテナであっても、デフォルトコンストラクタと emplace(key, mapped) を持っている場合、 toml::get で変換が可能です。

const auto t = toml::get<boost::container::flat_map<std::string, int>>(input.at("t"));

要素型の変換に失敗した場合は toml::type_error が送出されます。

toml::get_orを使って失敗時の値を指定する #

toml::get は変換に失敗した際に toml::type_error 例外を送出します。

toml::get_or を使用することで、変換に失敗した際に例外ではなくデフォルト値を返せるようになります。

toml::get<T> とは異なり、 get_or は引数から変換先の型を推論するため、 <T> の指定は不要です。

const auto a = toml::get_or(input.at("a"), 42);

変換可能な型は toml::get と同様です。

toml::value::xxx_typeを指定した場合は、参照を取り出すことも可能ですが、その場合は引数も参照である必要があります。

toml::value::integer_type a_default = 42;
auto a& = toml::get_or(input.at("a"), a_default);

toml::find<T>を使って検索と変換を同時に行う #

toml::find<T> は、テーブルを持つ toml::value から値を検索し、同時に toml::get と同じ型変換を行って取り出す関数です。

const auto a = toml::find<int>(input, "a");
// const auto a = toml::get<int>(input.at("a")); と同じ

toml::find<T> は配列にも使用できます。

const auto a  = input.at("a");
const auto a2 = toml::find<int>(a, 2);
// const auto a2 = toml::get<int>(input.at("a").at(2)); と同じ

型変換の際にエラーが起きた場合、 toml::get と同じ toml::type_error を 送出します。 キーが見つからなかった場合またはインデックスが存在しない場合は、 std::out_of_range を送出します。

型を指定しなかった場合、型変換を行わず toml::value を取り出します。

const auto a = toml::find(input, "a");
// const auto a = input.at("a"); と同じ

toml::find<T> は再帰的に値にアクセスることもできます。

a = {b = {c = 42}}
const auto a_b_c = toml::find<int>(input, "a", "b", "c");
// const auto a = toml::get<int>(input.at("a").at("b").at("c")); と同じ

このとき、キーとインデックスを混ぜることができます。

a = [ {b = 1}, {b = 2}, {b = 3} ]
const auto a_2_b = toml::find<int>(input, "a", 2, "b");
// const auto a = toml::get<int>(input.at("a").at(2).at("c")); と同じ

TOMLはquoted keyという機能を持っています。

これは、 ""'' を使うことで通常許可されない文字をキーに使えるというもので、 この中では . はテーブルを導入しません

"127.0.0.1" = "value"
site."google.com" = true

このTOMLファイルは以下のようにして読みます。

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());

このような場合にも違和感なく対応するため、toml11ではキーに . が含まれていても 自動で分割はしません。

テーブルの階層構造を陽に指定することが、適切な入力ファイルの構造化に資すると考えているからです。

参考: toml.io キー

toml::find_orを使って失敗時の値を指定する #

toml::find_or は、 toml::get_or と同様に、失敗時のデフォルト値を渡します。

const auto a = toml::find_or(input, "a", 42);

型変換の失敗だけでなく、キーが見つからなかった場合もデフォルト値を返します。

ユーザー定義型との変換を定義する #

toml::gettoml::find では、以下のどれかの方法を使うことで ユーザー定義型を使用することができます。

toml::from の定義 #

toml11には toml::from という型があり、以下のように特殊化することでユーザー定義型からの 変換をサポートできます。

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

後述する型設定を変更した toml::value もサポートする場合、以下のようにしてください。

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

この定義は、 TOML11_DEFINE_CONVERSION_NON_INTRUSIVE によって自動的に定義できます。

namespace extlib
{
struct foo
{
    int a;
    std::string b;
};
} // extlib

TOML11_DEFINE_CONVERSION_NON_INTRUSIVE(extlib::foo, a, b)

あるいは、リフレクションライブラリを使用することもできます。 exampleboost-ext/reflect を使用したサンプルも参照してください。

from_toml メンバ関数の定義 #

from_toml メンバ関数を定義することによっても変換を定義することができます。

これを使用する場合、デフォルトコンストラクタが必要です。

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 ;
    }
};

両方が定義されていた場合、 toml::from が優先されます。

toml::value を受け取るコンストラクタ #

toml::value を受け取るコンストラクタがあれば、 toml::get による変換ができます。

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;
};

両方が定義されていた場合、toml::fromfrom_toml が優先されます。

toml::visitで関数を適用する #

toml::value が格納する型すべてに適用できる関数オブジェクトがあれば、 toml::visit によって型変換を経ずに直接その関数を呼ぶことができます。

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

toml::value を構築する #

toml::value はパーサの内部だけでなく、ユーザーコードで構築することも可能です。

toml::value が格納する型と同じか変換可能な型を渡しての構築が可能です。

toml::value v1(true);
toml::value v2(42);
toml::value v3(3.14);

配列の場合、 toml::array を使うか、

toml::value v(toml::array{1, 2, 3});

配列の場合、 std::vector などのコンテナを直接渡すことが可能です。

const std::vector<toml::value> a{1,2,3};
toml::value v(a);

このコンテナには、 toml::get で変換可能なコンテナが使用できます。

テーブルの場合も同様に、 toml::table を使うか、

toml::value v(toml::table{{"foo", 1}, {"bar", 2}, {"baz", 3}});

std::map などのコンテナを直接渡します。

const std::map<std::string, toml::value> t{
        {"foo", 1}, {"bar", 2}, {"baz", 3}
    }
toml::value v(t);

コンストラクタには、 format_info と コメントを渡すことができます。

コメントの型は std::vector<std::string> です。 各要素が一行分に相当します。

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!"});

toml::value に変換する #

ユーザー定義型から toml::value を構築する際に、 toml::intointo_toml を 定義することで、その挙動をカスタマイズできます。

特に、別のライブラリの型などを変換する際に toml::into が便利です。

toml::intoを定義する #

toml::into を特殊化することで toml::value への変換が可能になります。

toml::value への変換が用意されていない外部ライブラリの型などに対して有効です。

toml::value が変換時に type_config を渡すため、basic_valuetemplate 引数を受け取る必要があります。

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}});
    }
};

into_toml メンバ関数を定義する #

from_toml と同様、メンバ関数によっても変換を定義することができます。

toml::into が定義されていた場合、そちらが優先されます。

struct bar
{
    int a;
    std::string b;

    toml::value into_toml() const
    {
        return toml::value(toml::table{{"a", this->a}, {"b", this->b}});
    }
};