1 /* 2 * Copyright (c) 2015, Vsevolod Stakhov 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * * Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * * Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY AUTHOR ''AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY 17 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 */ 24 25 #pragma once 26 #include <string> 27 #include <vector> 28 #include <map> 29 #include <set> 30 #include <memory> 31 #include <iostream> 32 #include <tuple> 33 34 #include "ucl.h" 35 36 // C++11 API inspired by json11: https://github.com/dropbox/json11/ 37 38 namespace ucl { 39 40 struct ucl_map_construct_t { }; 41 constexpr ucl_map_construct_t ucl_map_construct = ucl_map_construct_t(); 42 struct ucl_array_construct_t { }; 43 constexpr ucl_array_construct_t ucl_array_construct = ucl_array_construct_t(); 44 45 class Ucl final { 46 private: 47 48 struct ucl_deleter { 49 void operator() (ucl_object_t *obj) { 50 ucl_object_unref (obj); 51 } 52 }; 53 54 static int 55 append_char (unsigned char c, size_t nchars, void *ud) 56 { 57 std::string *out = reinterpret_cast<std::string *>(ud); 58 59 out->append (nchars, (char)c); 60 61 return nchars; 62 } 63 static int 64 append_len (unsigned const char *str, size_t len, void *ud) 65 { 66 std::string *out = reinterpret_cast<std::string *>(ud); 67 68 out->append ((const char *)str, len); 69 70 return len; 71 } 72 static int 73 append_int (int64_t elt, void *ud) 74 { 75 std::string *out = reinterpret_cast<std::string *>(ud); 76 auto nstr = std::to_string (elt); 77 78 out->append (nstr); 79 80 return nstr.size (); 81 } 82 static int 83 append_double (double elt, void *ud) 84 { 85 std::string *out = reinterpret_cast<std::string *>(ud); 86 auto nstr = std::to_string (elt); 87 88 out->append (nstr); 89 90 return nstr.size (); 91 } 92 93 static struct ucl_emitter_functions default_emit_funcs() 94 { 95 struct ucl_emitter_functions func = { 96 Ucl::append_char, 97 Ucl::append_len, 98 Ucl::append_int, 99 Ucl::append_double, 100 nullptr, 101 nullptr 102 }; 103 104 return func; 105 }; 106 107 static bool ucl_variable_getter(const unsigned char *data, size_t len, 108 unsigned char ** /*replace*/, size_t * /*replace_len*/, bool *need_free, void* ud) 109 { 110 *need_free = false; 111 112 auto vars = reinterpret_cast<std::set<std::string> *>(ud); 113 if (vars && data && len != 0) { 114 vars->emplace (data, data + len); 115 } 116 return false; 117 } 118 119 static bool ucl_variable_replacer (const unsigned char *data, size_t len, 120 unsigned char **replace, size_t *replace_len, bool *need_free, void* ud) 121 { 122 *need_free = false; 123 124 auto replacer = reinterpret_cast<variable_replacer *>(ud); 125 if (!replacer) { 126 return false; 127 } 128 129 std::string var_name (data, data + len); 130 if (!replacer->is_variable (var_name)) { 131 return false; 132 } 133 134 std::string var_value = replacer->replace (var_name); 135 if (var_value.empty ()) { 136 return false; 137 } 138 139 *replace = (unsigned char *)UCL_ALLOC (var_value.size ()); 140 memcpy (*replace, var_value.data (), var_value.size ()); 141 142 *replace_len = var_value.size (); 143 *need_free = true; 144 145 return true; 146 } 147 148 template <typename C, typename P> 149 static Ucl parse_with_strategy_function (C config_func, P parse_func, std::string &err) 150 { 151 auto parser = ucl_parser_new (UCL_PARSER_DEFAULT); 152 153 config_func (parser); 154 155 if (!parse_func (parser)) { 156 const char *error = ucl_parser_get_error (parser); //Assigning here without checking result first causes a 157 if( error != NULL ) err.assign(error); // crash if ucl_parser_get_error returns NULL 158 ucl_parser_free (parser); 159 160 return nullptr; 161 } 162 163 auto obj = ucl_parser_get_object (parser); 164 ucl_parser_free (parser); 165 166 // Obj will handle ownership 167 return Ucl (obj); 168 } 169 170 std::unique_ptr<ucl_object_t, ucl_deleter> obj; 171 172 public: 173 struct macro_handler_s { 174 ucl_macro_handler handler; 175 ucl_context_macro_handler ctx_handler; 176 }; 177 178 struct macro_userdata_s { 179 ucl_parser *parser; 180 void *userdata; 181 }; 182 183 class const_iterator { 184 private: 185 struct ucl_iter_deleter { 186 void operator() (ucl_object_iter_t it) { 187 ucl_object_iterate_free (it); 188 } 189 }; 190 std::shared_ptr<void> it; 191 std::unique_ptr<Ucl> cur; 192 public: 193 typedef std::forward_iterator_tag iterator_category; 194 195 const_iterator(const Ucl &obj) { 196 it = std::shared_ptr<void>(ucl_object_iterate_new (obj.obj.get()), 197 ucl_iter_deleter()); 198 cur.reset (new Ucl(ucl_object_iterate_safe (it.get(), true))); 199 if (!cur->obj) { 200 it.reset (); 201 cur.reset (); 202 } 203 } 204 205 const_iterator() {} 206 const_iterator(const const_iterator &other) = delete; 207 const_iterator(const_iterator &&other) = default; 208 ~const_iterator() {} 209 210 const_iterator& operator=(const const_iterator &other) = delete; 211 const_iterator& operator=(const_iterator &&other) = default; 212 213 bool operator==(const const_iterator &other) const 214 { 215 if (cur && other.cur) { 216 return cur->obj.get() == other.cur->obj.get(); 217 } 218 219 return !cur && !other.cur; 220 } 221 222 bool operator!=(const const_iterator &other) const 223 { 224 return !(*this == other); 225 } 226 227 const_iterator& operator++() 228 { 229 if (it) { 230 cur.reset (new Ucl(ucl_object_iterate_safe (it.get(), true))); 231 } 232 233 if (cur && !cur->obj) { 234 it.reset (); 235 cur.reset (); 236 } 237 238 return *this; 239 } 240 241 const Ucl& operator*() const 242 { 243 return *cur; 244 } 245 const Ucl* operator->() const 246 { 247 return cur.get(); 248 } 249 }; 250 251 struct variable_replacer { 252 virtual ~variable_replacer() {} 253 254 virtual bool is_variable (const std::string &str) const 255 { 256 return !str.empty (); 257 } 258 259 virtual std::string replace (const std::string &var) const = 0; 260 }; 261 262 // We grab ownership if get non-const ucl_object_t 263 Ucl(ucl_object_t *other) { 264 obj.reset (other); 265 } 266 267 // Shared ownership 268 Ucl(const ucl_object_t *other) { 269 obj.reset (ucl_object_ref (other)); 270 } 271 272 Ucl(const Ucl &other) { 273 obj.reset (ucl_object_ref (other.obj.get())); 274 } 275 276 Ucl(Ucl &&other) { 277 obj.swap (other.obj); 278 } 279 280 Ucl() noexcept { 281 obj.reset (ucl_object_typed_new (UCL_NULL)); 282 } 283 Ucl(std::nullptr_t) noexcept { 284 obj.reset (ucl_object_typed_new (UCL_NULL)); 285 } 286 Ucl(double value) { 287 obj.reset (ucl_object_typed_new (UCL_FLOAT)); 288 obj->value.dv = value; 289 } 290 Ucl(int64_t value) { 291 obj.reset (ucl_object_typed_new (UCL_INT)); 292 obj->value.iv = value; 293 } 294 Ucl(bool value) { 295 obj.reset (ucl_object_typed_new (UCL_BOOLEAN)); 296 obj->value.iv = static_cast<int64_t>(value); 297 } 298 Ucl(const std::string &value) { 299 obj.reset (ucl_object_fromstring_common (value.data (), value.size (), 300 UCL_STRING_RAW)); 301 } 302 Ucl(const char *value) { 303 obj.reset (ucl_object_fromstring_common (value, 0, UCL_STRING_RAW)); 304 } 305 306 // Implicit constructor: anything with a to_json() function. 307 template <class T, class = decltype(&T::to_ucl)> 308 Ucl(const T &t) : Ucl(t.to_ucl()) {} 309 310 // Implicit constructor: map-like objects (std::map, std::unordered_map, etc) 311 template <class M, typename std::enable_if< 312 std::is_constructible<std::string, typename M::key_type>::value 313 && std::is_constructible<Ucl, typename M::mapped_type>::value, 314 int>::type = 0> 315 Ucl(const M &m) { 316 obj.reset (ucl_object_typed_new (UCL_OBJECT)); 317 auto cobj = obj.get (); 318 319 for (const auto &e : m) { 320 ucl_object_insert_key (cobj, ucl_object_ref (e.second.obj.get()), 321 e.first.data (), e.first.size (), true); 322 } 323 } 324 325 // Implicit constructor: vector-like objects (std::list, std::vector, std::set, etc) 326 template <class V, typename std::enable_if< 327 std::is_constructible<Ucl, typename V::value_type>::value, 328 int>::type = 0> 329 Ucl(const V &v) { 330 obj.reset (ucl_object_typed_new (UCL_ARRAY)); 331 auto cobj = obj.get (); 332 333 for (const auto &e : v) { 334 ucl_array_append (cobj, ucl_object_ref (e.obj.get())); 335 } 336 } 337 338 ucl_type_t type () const { 339 if (obj) { 340 return ucl_object_type (obj.get ()); 341 } 342 return UCL_NULL; 343 } 344 345 std::string key () const { 346 std::string res; 347 348 if (obj->key) { 349 res.assign (obj->key, obj->keylen); 350 } 351 352 return res; 353 } 354 355 double number_value (const double default_val = 0.0) const 356 { 357 double res; 358 359 if (ucl_object_todouble_safe(obj.get(), &res)) { 360 return res; 361 } 362 363 return default_val; 364 } 365 366 int64_t int_value (const int64_t default_val = 0) const 367 { 368 int64_t res; 369 370 if (ucl_object_toint_safe(obj.get(), &res)) { 371 return res; 372 } 373 374 return default_val; 375 } 376 377 bool bool_value (const bool default_val = false) const 378 { 379 bool res; 380 381 if (ucl_object_toboolean_safe(obj.get(), &res)) { 382 return res; 383 } 384 385 return default_val; 386 } 387 388 std::string string_value (const std::string& default_val = "") const 389 { 390 const char* res = nullptr; 391 392 if (ucl_object_tostring_safe(obj.get(), &res)) { 393 return res; 394 } 395 396 return default_val; 397 } 398 399 size_t size () const 400 { 401 if (type () == UCL_ARRAY) { 402 return ucl_array_size (obj.get()); 403 } 404 405 return 0; 406 } 407 408 Ucl at (size_t i) const 409 { 410 if (type () == UCL_ARRAY) { 411 return Ucl (ucl_array_find_index (obj.get(), i)); 412 } 413 414 return Ucl (nullptr); 415 } 416 417 Ucl lookup (const std::string &key) const 418 { 419 if (type () == UCL_OBJECT) { 420 return Ucl (ucl_object_lookup_len (obj.get(), 421 key.data (), key.size ())); 422 } 423 424 return Ucl (nullptr); 425 } 426 427 inline Ucl operator[] (size_t i) const 428 { 429 return at(i); 430 } 431 432 inline Ucl operator[](const std::string &key) const 433 { 434 return lookup(key); 435 } 436 // Serialize. 437 void dump (std::string &out, ucl_emitter_t type = UCL_EMIT_JSON) const 438 { 439 struct ucl_emitter_functions cbdata; 440 441 cbdata = Ucl::default_emit_funcs(); 442 cbdata.ud = reinterpret_cast<void *>(&out); 443 444 ucl_object_emit_full (obj.get(), type, &cbdata, nullptr); 445 } 446 447 std::string dump (ucl_emitter_t type = UCL_EMIT_JSON) const 448 { 449 std::string out; 450 451 dump (out, type); 452 453 return out; 454 } 455 456 static Ucl parse (const std::string &in, std::string &err, enum ucl_duplicate_strategy duplicate_strategy = UCL_DUPLICATE_APPEND) 457 { 458 return parse (in, std::map<std::string, std::string>(), err, duplicate_strategy); 459 } 460 461 static Ucl parse (const std::string &in, const std::map<std::string, std::string> &vars, 462 std::string &err, enum ucl_duplicate_strategy duplicate_strategy = UCL_DUPLICATE_APPEND) 463 { 464 std::vector< std::tuple< std::string, macro_handler_s, void * > > emptyVector; 465 return parse ( in, vars, emptyVector, err, duplicate_strategy ); 466 } 467 468 //Macro handler will receive a macro_userdata_s as void *ud 469 static Ucl parse (const std::string &in, 470 std::vector< std::tuple< std::string /*name*/, macro_handler_s, void * /*userdata*/ > > ¯os, 471 std::string &err, enum ucl_duplicate_strategy duplicate_strategy = UCL_DUPLICATE_APPEND) 472 { 473 return parse (in, std::map<std::string, std::string>(), macros, err, duplicate_strategy); 474 } 475 476 //Macro handler will receive a macro_userdata_s as void *ud 477 static Ucl parse (const std::string &in, const std::map<std::string, std::string> &vars, 478 std::vector< std::tuple< std::string /*name*/, macro_handler_s, void * /*userdata*/ > > ¯os, 479 std::string &err, enum ucl_duplicate_strategy duplicate_strategy = UCL_DUPLICATE_APPEND) 480 { 481 //Preserve macro_userdata_s memory for later use in parse_with_strategy_function() 482 std::vector<macro_userdata_s> userdata_list; 483 userdata_list.reserve (macros.size()); 484 auto config_func = [&userdata_list, &vars, ¯os] (ucl_parser *parser) { 485 for (const auto & item : vars) { 486 ucl_parser_register_variable (parser, item.first.c_str (), item.second.c_str ()); 487 } 488 for (auto & macro : macros) { 489 userdata_list.push_back ({parser, std::get<2>(macro)}); 490 if (std::get<1>(macro).handler != NULL) { 491 ucl_parser_register_macro (parser, 492 std::get<0>(macro).c_str(), 493 std::get<1>(macro).handler, 494 reinterpret_cast<void*>(&userdata_list.back())); 495 } 496 else if (std::get<1>(macro).ctx_handler != NULL) { 497 ucl_parser_register_context_macro (parser, 498 std::get<0>(macro).c_str(), 499 std::get<1>(macro).ctx_handler, 500 reinterpret_cast<void*>(&userdata_list.back())); 501 } 502 } 503 }; 504 505 auto parse_func = [&in, &duplicate_strategy] (struct ucl_parser *parser) -> bool { 506 return ucl_parser_add_chunk_full (parser, 507 (unsigned char *) in.data (), 508 in.size (), 509 (unsigned int)ucl_parser_get_default_priority (parser), 510 duplicate_strategy, 511 UCL_PARSE_UCL); 512 }; 513 514 return parse_with_strategy_function (config_func, parse_func, err); 515 } 516 517 static Ucl parse (const std::string &in, const variable_replacer &replacer, 518 std::string &err, enum ucl_duplicate_strategy duplicate_strategy = UCL_DUPLICATE_APPEND) 519 { 520 std::vector< std::tuple< std::string, macro_handler_s, void * > > emptyVector; 521 return parse ( in, replacer, emptyVector, err, duplicate_strategy ); 522 } 523 524 //Macro handler will receive a macro_userdata_s as void *ud 525 static Ucl parse (const std::string &in, const variable_replacer &replacer, 526 std::vector< std::tuple< std::string /*name*/, macro_handler_s, void * /*userdata*/ > > ¯os, 527 std::string &err, enum ucl_duplicate_strategy duplicate_strategy = UCL_DUPLICATE_APPEND) 528 { 529 //Preserve macro_userdata_s memory for later use in parse_with_strategy_function() 530 std::vector<macro_userdata_s> userdata_list; 531 userdata_list.reserve (macros.size()); 532 auto config_func = [&userdata_list, &replacer, ¯os] (ucl_parser *parser) { 533 ucl_parser_set_variables_handler (parser, ucl_variable_replacer, &const_cast<variable_replacer &>(replacer)); 534 for (auto & macro : macros) { 535 userdata_list.push_back ({parser, std::get<2>(macro)}); 536 if (std::get<1>(macro).handler != NULL) { 537 ucl_parser_register_macro (parser, 538 std::get<0>(macro).c_str(), 539 std::get<1>(macro).handler, 540 reinterpret_cast<void*>(&userdata_list.back())); 541 } 542 else if (std::get<1>(macro).ctx_handler != NULL) { 543 ucl_parser_register_context_macro (parser, 544 std::get<0>(macro).c_str(), 545 std::get<1>(macro).ctx_handler, 546 reinterpret_cast<void*>(&userdata_list.back())); 547 } 548 } 549 }; 550 551 auto parse_func = [&in, &duplicate_strategy] (struct ucl_parser *parser) -> bool { 552 return ucl_parser_add_chunk_full (parser, 553 (unsigned char *) in.data (), 554 in.size (), 555 (unsigned int)ucl_parser_get_default_priority (parser), 556 duplicate_strategy, 557 UCL_PARSE_UCL); 558 }; 559 560 return parse_with_strategy_function (config_func, parse_func, err); 561 } 562 563 static Ucl parse (const char *in, std::string &err, enum ucl_duplicate_strategy duplicate_strategy = UCL_DUPLICATE_APPEND) 564 { 565 return parse (in, std::map<std::string, std::string>(), err, duplicate_strategy); 566 } 567 568 static Ucl parse (const char *in, const std::map<std::string, std::string> &vars, std::string &err) 569 { 570 if (!in) { 571 err = "null input"; 572 return nullptr; 573 } 574 return parse (std::string (in), vars, err); 575 } 576 577 //Macro handler will receive a macro_userdata_s as void *ud 578 static Ucl parse (const char *in, 579 std::vector< std::tuple< std::string /*name*/, macro_handler_s, void * /*userdata*/ > > ¯os, 580 std::string &err, enum ucl_duplicate_strategy duplicate_strategy = UCL_DUPLICATE_APPEND) 581 { 582 return parse (in, std::map<std::string, std::string>(), macros, err, duplicate_strategy); 583 } 584 585 //Macro handler will receive a macro_userdata_s as void *ud 586 static Ucl parse (const char *in, const std::map<std::string, std::string> &vars, 587 std::vector< std::tuple< std::string /*name*/, macro_handler_s, void * /*userdata*/ > > ¯os, 588 std::string &err, enum ucl_duplicate_strategy duplicate_strategy = UCL_DUPLICATE_APPEND) 589 { 590 if (!in) { 591 err = "null input"; 592 return nullptr; 593 } 594 return parse (std::string (in), vars, macros, err, duplicate_strategy); 595 } 596 597 static Ucl parse (const char *in, const variable_replacer &replacer, 598 std::string &err, enum ucl_duplicate_strategy duplicate_strategy = UCL_DUPLICATE_APPEND) 599 { 600 if (!in) { 601 err = "null input"; 602 return nullptr; 603 } 604 return parse (std::string(in), replacer, err, duplicate_strategy); 605 } 606 607 //Macro handler will receive a macro_userdata_s as void *ud 608 static Ucl parse (const char *in, const variable_replacer &replacer, 609 std::vector< std::tuple< std::string /*name*/, macro_handler_s, void * /*userdata*/ > > ¯os, 610 std::string &err, enum ucl_duplicate_strategy duplicate_strategy = UCL_DUPLICATE_APPEND) 611 { 612 if (!in) { 613 err = "null input"; 614 return nullptr; 615 } 616 return parse (std::string (in), replacer, macros, err, duplicate_strategy); 617 } 618 619 static Ucl parse_from_file (const std::string &filename, std::string &err) 620 { 621 return parse_from_file (filename, std::map<std::string, std::string>(), err); 622 } 623 624 static Ucl parse_from_file (const std::string &filename, const std::map<std::string, std::string> &vars, std::string &err) 625 { 626 auto config_func = [&vars] (ucl_parser *parser) { 627 for (const auto & item : vars) { 628 ucl_parser_register_variable (parser, item.first.c_str (), item.second.c_str ()); 629 } 630 }; 631 632 auto parse_func = [&filename] (ucl_parser *parser) { 633 return ucl_parser_add_file (parser, filename.c_str ()); 634 }; 635 636 return parse_with_strategy_function (config_func, parse_func, err); 637 } 638 639 static Ucl parse_from_file (const std::string &filename, const variable_replacer &replacer, std::string &err) 640 { 641 auto config_func = [&replacer] (ucl_parser *parser) { 642 ucl_parser_set_variables_handler (parser, ucl_variable_replacer, 643 &const_cast<variable_replacer &>(replacer)); 644 }; 645 646 auto parse_func = [&filename] (ucl_parser *parser) { 647 return ucl_parser_add_file (parser, filename.c_str ()); 648 }; 649 650 return parse_with_strategy_function (config_func, parse_func, err); 651 } 652 653 static std::vector<std::string> find_variable (const std::string &in) 654 { 655 auto parser = ucl_parser_new (UCL_PARSER_DEFAULT); 656 657 std::set<std::string> vars; 658 ucl_parser_set_variables_handler (parser, ucl_variable_getter, &vars); 659 ucl_parser_add_chunk (parser, (const unsigned char *)in.data (), in.size ()); 660 ucl_parser_free (parser); 661 662 std::vector<std::string> result; 663 std::move (vars.begin (), vars.end (), std::back_inserter (result)); 664 return result; 665 } 666 667 static std::vector<std::string> find_variable (const char *in) 668 { 669 if (!in) { 670 return std::vector<std::string>(); 671 } 672 return find_variable (std::string (in)); 673 } 674 675 static std::vector<std::string> find_variable_from_file (const std::string &filename) 676 { 677 auto parser = ucl_parser_new (UCL_PARSER_DEFAULT); 678 679 std::set<std::string> vars; 680 ucl_parser_set_variables_handler (parser, ucl_variable_getter, &vars); 681 ucl_parser_add_file (parser, filename.c_str ()); 682 ucl_parser_free (parser); 683 684 std::vector<std::string> result; 685 std::move (vars.begin (), vars.end (), std::back_inserter (result)); 686 return result; 687 } 688 689 Ucl& operator= (Ucl rhs) 690 { 691 obj.swap (rhs.obj); 692 return *this; 693 } 694 695 bool operator== (const Ucl &rhs) const 696 { 697 return ucl_object_compare (obj.get(), rhs.obj.get ()) == 0; 698 } 699 bool operator< (const Ucl &rhs) const 700 { 701 return ucl_object_compare (obj.get(), rhs.obj.get ()) < 0; 702 } 703 bool operator!= (const Ucl &rhs) const { return !(*this == rhs); } 704 bool operator<= (const Ucl &rhs) const { return !(rhs < *this); } 705 bool operator> (const Ucl &rhs) const { return (rhs < *this); } 706 bool operator>= (const Ucl &rhs) const { return !(*this < rhs); } 707 708 explicit operator bool () const 709 { 710 if (!obj || type() == UCL_NULL) { 711 return false; 712 } 713 714 if (type () == UCL_BOOLEAN) { 715 return bool_value (); 716 } 717 718 return true; 719 } 720 721 const_iterator begin() const 722 { 723 return const_iterator(*this); 724 } 725 const_iterator cbegin() const 726 { 727 return const_iterator(*this); 728 } 729 const_iterator end() const 730 { 731 return const_iterator(); 732 } 733 const_iterator cend() const 734 { 735 return const_iterator(); 736 } 737 }; 738 739 }; 740