String.h 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839
  1. // Copyright (C) 2009-present, Panagiotis Christopoulos Charitos and contributors.
  2. // All rights reserved.
  3. // Code licensed under the BSD License.
  4. // http://www.anki3d.org/LICENSE
  5. #pragma once
  6. #include <AnKi/Util/DynamicArray.h>
  7. #include <AnKi/Util/Array.h>
  8. #include <AnKi/Util/Hash.h>
  9. #include <AnKi/Util/Forward.h>
  10. #include <cstring>
  11. #include <cstdio>
  12. #include <cctype>
  13. #include <cinttypes> // For PRId8 etc
  14. #include <cstdarg> // For var args
  15. #include <cstdlib>
  16. namespace anki {
  17. /// @addtogroup util_private
  18. /// @{
  19. namespace detail {
  20. template<typename TNumber>
  21. constexpr const char* toStringFormat()
  22. {
  23. return nullptr;
  24. }
  25. #define ANKI_DEPLOY_TO_STRING(type_, string_) \
  26. template<> \
  27. constexpr const char* toStringFormat<type_>() \
  28. { \
  29. return string_; \
  30. }
  31. ANKI_DEPLOY_TO_STRING(I8, "%" PRId8)
  32. ANKI_DEPLOY_TO_STRING(I16, "%" PRId16)
  33. ANKI_DEPLOY_TO_STRING(I32, "%" PRId32)
  34. ANKI_DEPLOY_TO_STRING(I64, "%" PRId64)
  35. ANKI_DEPLOY_TO_STRING(U8, "%" PRIu8)
  36. ANKI_DEPLOY_TO_STRING(U16, "%" PRIu16)
  37. ANKI_DEPLOY_TO_STRING(U32, "%" PRIu32)
  38. ANKI_DEPLOY_TO_STRING(U64, "%" PRIu64)
  39. ANKI_DEPLOY_TO_STRING(F32, "%f")
  40. ANKI_DEPLOY_TO_STRING(F64, "%f")
  41. #undef ANKI_DEPLOY_TO_STRING
  42. } // end namespace detail
  43. /// @}
  44. /// @addtogroup util_containers
  45. /// @{
  46. /// A wrapper on top of C strings. Used mainly for safety.
  47. class CString
  48. {
  49. public:
  50. using Char = char;
  51. static constexpr PtrSize kNpos = kMaxPtrSize;
  52. CString() = default;
  53. constexpr CString(const Char* ptr)
  54. : m_ptr(ptr)
  55. {
  56. }
  57. /// Copy constructor.
  58. constexpr CString(const CString& b)
  59. : m_ptr(b.m_ptr)
  60. {
  61. }
  62. /// Copy.
  63. constexpr CString& operator=(const CString& b)
  64. {
  65. m_ptr = b.m_ptr;
  66. return *this;
  67. }
  68. /// Return true if the string is initialized.
  69. explicit operator Bool() const
  70. {
  71. return !isEmpty();
  72. }
  73. /// Return char at the specified position.
  74. template<typename T>
  75. const Char& operator[](T pos) const
  76. {
  77. checkInit();
  78. ANKI_ASSERT(pos >= 0 && U32(pos) <= getLength());
  79. return m_ptr[pos];
  80. }
  81. Bool operator==(const CString& b) const
  82. {
  83. if(m_ptr == nullptr || b.m_ptr == nullptr)
  84. {
  85. return m_ptr == b.m_ptr;
  86. }
  87. else
  88. {
  89. return std::strcmp(m_ptr, b.m_ptr) == 0;
  90. }
  91. }
  92. Bool operator!=(const CString& b) const
  93. {
  94. return !((*this) == b);
  95. }
  96. Bool operator<(const CString& b) const
  97. {
  98. if(m_ptr == nullptr || b.m_ptr == nullptr)
  99. {
  100. return false;
  101. }
  102. else
  103. {
  104. return std::strcmp(m_ptr, b.m_ptr) < 0;
  105. }
  106. }
  107. Bool operator<=(const CString& b) const
  108. {
  109. if(m_ptr == nullptr || b.m_ptr == nullptr)
  110. {
  111. return m_ptr == b.m_ptr;
  112. }
  113. else
  114. {
  115. return std::strcmp(m_ptr, b.m_ptr) <= 0;
  116. }
  117. }
  118. Bool operator>(const CString& b) const
  119. {
  120. if(m_ptr == nullptr || b.m_ptr == nullptr)
  121. {
  122. return false;
  123. }
  124. else
  125. {
  126. return std::strcmp(m_ptr, b.m_ptr) > 0;
  127. }
  128. }
  129. Bool operator>=(const CString& b) const
  130. {
  131. if(m_ptr == nullptr || b.m_ptr == nullptr)
  132. {
  133. return m_ptr == b.m_ptr;
  134. }
  135. else
  136. {
  137. return std::strcmp(m_ptr, b.m_ptr) >= 0;
  138. }
  139. }
  140. /// Get C-string.
  141. const Char* cstr() const
  142. {
  143. return (m_ptr) ? m_ptr : "";
  144. }
  145. const Char* getBegin() const
  146. {
  147. checkInit();
  148. return &m_ptr[0];
  149. }
  150. const Char* getEnd() const
  151. {
  152. checkInit();
  153. return &m_ptr[getLength()];
  154. }
  155. const Char* begin() const
  156. {
  157. return getBegin();
  158. }
  159. const Char* end() const
  160. {
  161. return getEnd();
  162. }
  163. /// Return true if the string is not initialized.
  164. Bool isEmpty() const
  165. {
  166. return m_ptr == nullptr || getLength() == 0;
  167. }
  168. /// Get the string length.
  169. U32 getLength() const
  170. {
  171. return (m_ptr == nullptr) ? 0 : U32(std::strlen(m_ptr));
  172. }
  173. /// Find a substring of this string.
  174. /// @param[in] cstr The substring to search.
  175. /// @param position Position of the first character in the string to be considered in the search.
  176. /// @return A valid position if the string is found or kNpos if not found.
  177. PtrSize find(const CString& cstr, PtrSize position = 0) const
  178. {
  179. checkInit();
  180. ANKI_ASSERT(position < getLength());
  181. const Char* out = std::strstr(m_ptr + position, &cstr[0]);
  182. return (out == nullptr) ? kNpos : PtrSize(out - m_ptr);
  183. }
  184. /// Convert to F16.
  185. Error toNumber(F16& out) const;
  186. /// Convert to F32.
  187. Error toNumber(F32& out) const;
  188. /// Convert to F64.
  189. Error toNumber(F64& out) const;
  190. /// Convert to I8.
  191. Error toNumber(I8& out) const;
  192. /// Convert to U8.
  193. Error toNumber(U8& out) const;
  194. /// Convert to I16.
  195. Error toNumber(I16& out) const;
  196. /// Convert to U16.
  197. Error toNumber(U16& out) const;
  198. /// Convert to I32.
  199. Error toNumber(I32& out) const;
  200. /// Convert to U32.
  201. Error toNumber(U32& out) const;
  202. /// Convert to I64.
  203. Error toNumber(I64& out) const;
  204. /// Convert to U64.
  205. Error toNumber(U64& out) const;
  206. /// Convert to Bool.
  207. Error toNumber(Bool& out) const;
  208. /// Compute the hash.
  209. U64 computeHash() const
  210. {
  211. checkInit();
  212. return anki::computeHash(m_ptr, getLength());
  213. }
  214. void toWideChars(WChar* arr, U32 arrSize) const
  215. {
  216. checkInit();
  217. const U32 len = getLength();
  218. ANKI_ASSERT(arrSize >= len + 1);
  219. if(len > 0)
  220. {
  221. [[maybe_unused]] const PtrSize charsWritten = mbstowcs(arr, m_ptr, arrSize);
  222. ANKI_ASSERT(charsWritten == len);
  223. }
  224. else
  225. {
  226. *arr = L'\0';
  227. }
  228. }
  229. private:
  230. const Char* m_ptr = nullptr;
  231. void checkInit() const
  232. {
  233. ANKI_ASSERT(m_ptr != nullptr);
  234. }
  235. };
  236. /// Compare function for CStrings. Can be used in HashMap.
  237. class CStringCompare
  238. {
  239. public:
  240. Bool operator()(CString a, CString b)
  241. {
  242. return a == b;
  243. }
  244. };
  245. /// The base class for strings.
  246. template<typename TMemoryPool>
  247. class BaseString
  248. {
  249. template<typename>
  250. friend class BaseStringList;
  251. public:
  252. using Char = char; ///< Character type
  253. using Iterator = Char*;
  254. using ConstIterator = const Char*;
  255. using MemoryPool = TMemoryPool;
  256. static constexpr PtrSize kNpos = kMaxPtrSize;
  257. /// Default ctor.
  258. BaseString(const MemoryPool& pool = MemoryPool())
  259. : m_data(pool)
  260. {
  261. }
  262. /// Copy ctor.
  263. BaseString(const BaseString& b)
  264. {
  265. *this = b;
  266. }
  267. /// Copy ctor.
  268. BaseString(CString str, const MemoryPool& pool = MemoryPool())
  269. : m_data(pool)
  270. {
  271. *this = str;
  272. }
  273. /// Copy ctor.
  274. BaseString(const Char* str, const MemoryPool& pool = MemoryPool())
  275. : BaseString(CString(str), pool)
  276. {
  277. }
  278. /// Move ctor.
  279. BaseString(BaseString&& b)
  280. : m_data(std::move(b.m_data))
  281. {
  282. }
  283. /// Initialize using a range. Copies the range of [first, last)
  284. BaseString(ConstIterator first, ConstIterator last, const MemoryPool& pool = MemoryPool())
  285. : m_data(pool)
  286. {
  287. ANKI_ASSERT(first != nullptr && last != nullptr);
  288. for(ConstIterator it = first; it < last; ++it)
  289. {
  290. ANKI_ASSERT(*it != '\0');
  291. }
  292. const PtrSize length = last - first;
  293. m_data.resize(length + 1);
  294. memcpy(&m_data[0], first, length);
  295. m_data[length] = '\0';
  296. }
  297. /// Initialize using a character.
  298. BaseString(Char c, PtrSize length, const MemoryPool& pool = MemoryPool())
  299. : m_data(pool)
  300. {
  301. ANKI_ASSERT(c != '\0');
  302. m_data.resize(length + 1);
  303. memset(&m_data[0], c, length);
  304. m_data[length] = '\0';
  305. }
  306. ~BaseString() = default;
  307. /// Move.
  308. BaseString& operator=(BaseString&& b)
  309. {
  310. m_data = std::move(b.m_data);
  311. return *this;
  312. }
  313. /// Copy.
  314. BaseString& operator=(const BaseString& b)
  315. {
  316. m_data = b.m_data;
  317. return *this;
  318. }
  319. /// Copy.
  320. BaseString& operator=(const Char* b)
  321. {
  322. *this = CString(b);
  323. return *this;
  324. }
  325. /// Copy.
  326. BaseString& operator=(CString str)
  327. {
  328. const U32 len = str.getLength();
  329. if(len > 0)
  330. {
  331. m_data.resize(len + 1);
  332. memcpy(&m_data[0], &str[0], sizeof(Char) * (len + 1));
  333. }
  334. else
  335. {
  336. m_data.destroy();
  337. }
  338. return *this;
  339. }
  340. /// Return char at the specified position.
  341. template<typename TInt>
  342. const Char& operator[](TInt pos) const
  343. {
  344. return m_data[pos];
  345. }
  346. /// Return char at the specified position as a modifiable reference.
  347. template<typename TInt>
  348. Char& operator[](TInt pos)
  349. {
  350. return m_data[pos];
  351. }
  352. explicit operator Bool() const
  353. {
  354. return !isEmpty();
  355. }
  356. Bool operator==(CString b) const
  357. {
  358. return toCString() == b;
  359. }
  360. Bool operator!=(CString b) const
  361. {
  362. return !(*this == b);
  363. }
  364. Bool operator<(CString b) const
  365. {
  366. return toCString() < b;
  367. }
  368. Bool operator<=(CString b) const
  369. {
  370. return toCString() < b;
  371. }
  372. Bool operator>(CString b) const
  373. {
  374. return toCString() > b;
  375. }
  376. Bool operator>=(CString b) const
  377. {
  378. return toCString() >= b;
  379. }
  380. /// Append another string to this one.
  381. BaseString& operator+=(CString b)
  382. {
  383. if(!b.isEmpty())
  384. {
  385. appendInternal(&b[0], b.getLength());
  386. }
  387. return *this;
  388. }
  389. BaseString operator+(CString b) const
  390. {
  391. BaseString out = *this;
  392. if(!b.isEmpty())
  393. {
  394. out.appendInternal(&b[0], b.getLength());
  395. }
  396. return out;
  397. }
  398. operator CString() const
  399. {
  400. return toCString();
  401. }
  402. /// Get a C string.
  403. const Char* cstr() const
  404. {
  405. return toCString().cstr();
  406. }
  407. /// Append using a range. Copies the range of [first, oneAfterLast)
  408. BaseString& append(ConstIterator first, ConstIterator oneAfterLast)
  409. {
  410. ANKI_ASSERT(oneAfterLast >= first);
  411. const PtrSize len = oneAfterLast - first;
  412. appendInternal(first, len);
  413. return *this;
  414. }
  415. /// Create formated string.
  416. ANKI_CHECK_FORMAT(1, 2)
  417. BaseString& sprintf(const Char* fmt, ...)
  418. {
  419. ANKI_ASSERT(fmt);
  420. va_list args;
  421. va_start(args, fmt);
  422. sprintfInternal(fmt, args);
  423. va_end(args);
  424. return *this;
  425. }
  426. /// Destroy the string.
  427. void destroy()
  428. {
  429. m_data.destroy();
  430. }
  431. Iterator getBegin()
  432. {
  433. ANKI_ASSERT(!isEmpty());
  434. return &m_data[0];
  435. }
  436. ConstIterator getBegin() const
  437. {
  438. ANKI_ASSERT(!isEmpty());
  439. return &m_data[0];
  440. }
  441. Iterator getEnd()
  442. {
  443. ANKI_ASSERT(!isEmpty());
  444. return &m_data[m_data.getSize() - 1];
  445. }
  446. ConstIterator getEnd() const
  447. {
  448. ANKI_ASSERT(!isEmpty());
  449. return &m_data[m_data.getSize() - 1];
  450. }
  451. Iterator begin()
  452. {
  453. return getBegin();
  454. }
  455. ConstIterator begin() const
  456. {
  457. return getBegin();
  458. }
  459. Iterator end()
  460. {
  461. return getEnd();
  462. }
  463. ConstIterator end() const
  464. {
  465. return getEnd();
  466. }
  467. /// Return the string's length. It doesn't count the terminating character.
  468. U32 getLength() const
  469. {
  470. return (m_data.getSize() == 0) ? 0 : U32(std::strlen(&m_data[0]));
  471. }
  472. /// Return the CString.
  473. CString toCString() const
  474. {
  475. return (!isEmpty()) ? CString(&m_data[0]) : CString();
  476. }
  477. /// Return true if it's empty.
  478. Bool isEmpty() const
  479. {
  480. return m_data.isEmpty();
  481. }
  482. /// Find a substring of this string.
  483. /// @param[in] cstr The substring to search.
  484. /// @param position Position of the first character in the string to be considered in the search.
  485. /// @return A valid position if the string is found or kNpos if not found.
  486. PtrSize find(const CString& cstr, PtrSize position = 0) const
  487. {
  488. ANKI_ASSERT(!isEmpty());
  489. return toCString().find(cstr, position);
  490. }
  491. /// Find a substring of this string.
  492. /// @param[in] str The substring to search.
  493. /// @param position Position of the first character in the string to be considered in the search.
  494. /// @return A valid position if the string is found or kNpos if not found.
  495. PtrSize find(const BaseString& str, PtrSize position) const
  496. {
  497. ANKI_ASSERT(!isEmpty());
  498. return find(str.toCString(), position);
  499. }
  500. /// Convert a number to a string.
  501. template<typename TNumber>
  502. void toString(TNumber number)
  503. {
  504. destroy();
  505. Array<Char, 512> buff;
  506. const I ret = std::snprintf(&buff[0], buff.size(), detail::toStringFormat<TNumber>(), number);
  507. if(ret < 0 || ret > I(buff.getSize()))
  508. {
  509. ANKI_UTIL_LOGF("To small intermediate buffer");
  510. }
  511. else
  512. {
  513. *this = CString(&buff[0]);
  514. }
  515. }
  516. /// Convert to F16.
  517. Error toNumber(F16& out) const
  518. {
  519. return toCString().toNumber(out);
  520. }
  521. /// Convert to F32.
  522. Error toNumber(F32& out) const
  523. {
  524. return toCString().toNumber(out);
  525. }
  526. /// Convert to F64.
  527. Error toNumber(F64& out) const
  528. {
  529. return toCString().toNumber(out);
  530. }
  531. /// Convert to I8.
  532. Error toNumber(I8& out) const
  533. {
  534. return toCString().toNumber(out);
  535. }
  536. /// Convert to U8.
  537. Error toNumber(U8& out) const
  538. {
  539. return toCString().toNumber(out);
  540. }
  541. /// Convert to I16.
  542. Error toNumber(I16& out) const
  543. {
  544. return toCString().toNumber(out);
  545. }
  546. /// Convert to U16.
  547. Error toNumber(U16& out) const
  548. {
  549. return toCString().toNumber(out);
  550. }
  551. /// Convert to I32.
  552. Error toNumber(I32& out) const
  553. {
  554. return toCString().toNumber(out);
  555. }
  556. /// Convert to U32.
  557. Error toNumber(U32& out) const
  558. {
  559. return toCString().toNumber(out);
  560. }
  561. /// Convert to I64.
  562. Error toNumber(I64& out) const
  563. {
  564. return toCString().toNumber(out);
  565. }
  566. /// Convert to U64.
  567. Error toNumber(U64& out) const
  568. {
  569. return toCString().toNumber(out);
  570. }
  571. /// Convert to Bool.
  572. Error toNumber(Bool& out) const
  573. {
  574. return toCString().toNumber(out);
  575. }
  576. /// Compute the hash.
  577. U64 computeHash() const
  578. {
  579. ANKI_ASSERT(!isEmpty());
  580. return toCString().computeHash();
  581. }
  582. /// Replace all occurrences of "from" with "to".
  583. BaseString& replaceAll(CString from, CString to)
  584. {
  585. BaseString tmp(toCString(), getMemoryPool());
  586. const PtrSize fromLen = from.getLength();
  587. const PtrSize toLen = to.getLength();
  588. PtrSize pos = kNpos;
  589. while((pos = tmp.find(from)) != kNpos)
  590. {
  591. BaseString tmp2(getMemoryPool());
  592. if(pos > 0)
  593. {
  594. tmp2.append(tmp.getBegin(), tmp.getBegin() + pos);
  595. }
  596. if(toLen > 0)
  597. {
  598. tmp2.append(to.getBegin(), to.getBegin() + toLen);
  599. }
  600. if(pos + fromLen < tmp.getLength())
  601. {
  602. tmp2.append(tmp.getBegin() + pos + fromLen, tmp.getEnd());
  603. }
  604. tmp.destroy();
  605. tmp = std::move(tmp2);
  606. }
  607. destroy();
  608. *this = std::move(tmp);
  609. return *this;
  610. }
  611. /// @brief Execute a functor for all characters of the string.
  612. template<typename TFunc>
  613. BaseString& transform(TFunc func)
  614. {
  615. U i = 0;
  616. while(i < m_data.getSize() && m_data[i] != '\0')
  617. {
  618. func(m_data[i]);
  619. ++i;
  620. }
  621. return *this;
  622. }
  623. BaseString& toLower()
  624. {
  625. return transform([](Char& c) {
  626. c = Char(tolower(c));
  627. });
  628. }
  629. TMemoryPool& getMemoryPool()
  630. {
  631. return m_data.getMemoryPool();
  632. }
  633. private:
  634. DynamicArray<Char, TMemoryPool, PtrSize> m_data;
  635. /// Append to this string.
  636. void appendInternal(const Char* str, PtrSize strLen)
  637. {
  638. ANKI_ASSERT(str != nullptr);
  639. ANKI_ASSERT(strLen > 0);
  640. auto size = m_data.getSize();
  641. // Fix empty string
  642. if(size == 0)
  643. {
  644. size = 1;
  645. }
  646. m_data.resize(size + strLen);
  647. memcpy(&m_data[size - 1], str, sizeof(Char) * strLen);
  648. m_data[m_data.getSize() - 1] = '\0';
  649. }
  650. /// Internal don't use it.
  651. void sprintfInternal(const Char* fmt, va_list& args)
  652. {
  653. Array<Char, 512> buffer;
  654. va_list args2;
  655. va_copy(args2, args); // vsnprintf will alter "args". Copy it case we need to call vsnprintf in the else bellow
  656. I len = std::vsnprintf(&buffer[0], sizeof(buffer), fmt, args);
  657. if(len < 0)
  658. {
  659. ANKI_UTIL_LOGF("vsnprintf() failed");
  660. }
  661. else if(PtrSize(len) >= sizeof(buffer))
  662. {
  663. I size = len + 1;
  664. m_data.resize(size);
  665. len = std::vsnprintf(&m_data[0], size, fmt, args2);
  666. ANKI_ASSERT((len + 1) == size);
  667. }
  668. else
  669. {
  670. // buffer was enough
  671. *this = CString(&buffer[0]);
  672. }
  673. va_end(args2);
  674. }
  675. };
  676. using String = BaseString<SingletonMemoryPoolWrapper<DefaultMemoryPool>>;
  677. #define ANKI_STRING_COMPARE_OPERATOR(TypeA, TypeB, op, ...) \
  678. __VA_ARGS__ \
  679. inline Bool operator op(TypeA a, TypeB b) \
  680. { \
  681. return CString(a) op CString(b); \
  682. }
  683. #define ANKI_STRING_COMPARE_OPS(TypeA, TypeB, ...) \
  684. ANKI_STRING_COMPARE_OPERATOR(TypeA, TypeB, ==, __VA_ARGS__) \
  685. ANKI_STRING_COMPARE_OPERATOR(TypeA, TypeB, !=, __VA_ARGS__) \
  686. ANKI_STRING_COMPARE_OPERATOR(TypeA, TypeB, <, __VA_ARGS__) \
  687. ANKI_STRING_COMPARE_OPERATOR(TypeA, TypeB, <=, __VA_ARGS__) \
  688. ANKI_STRING_COMPARE_OPERATOR(TypeA, TypeB, >, __VA_ARGS__) \
  689. ANKI_STRING_COMPARE_OPERATOR(TypeA, TypeB, >=, __VA_ARGS__)
  690. ANKI_STRING_COMPARE_OPS(const Char*, CString, )
  691. ANKI_STRING_COMPARE_OPS(const Char*, const BaseString<TMemPool>&, template<typename TMemPool>)
  692. ANKI_STRING_COMPARE_OPS(const BaseString<TMemPool>&, const Char*, template<typename TMemPool>)
  693. ANKI_STRING_COMPARE_OPS(CString, const BaseString<TMemPool>&, template<typename TMemPool>)
  694. ANKI_STRING_COMPARE_OPS(const BaseString<TMemPool>&, const BaseString<TMemPool>&, template<typename TMemPool>)
  695. #undef ANKI_STRING_COMPARE_OPERATOR
  696. #undef ANKI_STRING_COMPARE_OPS
  697. /// @}
  698. } // end namespace anki