stdex
Additional custom or not Standard C++ covered algorithms
Loading...
Searching...
No Matches
string.hpp
1/*
2 SPDX-License-Identifier: MIT
3 Copyright © 2016-2023 Amebis
4*/
5
6#pragma once
7
8#include "sal.hpp"
9#include <assert.h>
10#include <ctype.h>
11#include <stdarg.h>
12#include <stdint.h>
13#include <stdexcept>
14
15namespace stdex
16{
17#ifdef _WIN32
18 using locale_t = _locale_t;
19#else
20 using locale_t = ::locale_t;
21#endif
22
26#ifdef _WIN32
27 typedef wchar_t utf16_t;
28#else
29 typedef char16_t utf16_t;
30#endif
31
37 inline bool is_high_surrogate(_In_ utf16_t chr)
38 {
39 return 0xd800 < chr && chr < 0xdc00;
40 }
41
47 inline bool is_low_surrogate(_In_ utf16_t chr)
48 {
49 return 0xdc00 < chr && chr < 0xe000;
50 }
51
57 inline bool is_surrogate_pair(_In_reads_(2) const utf16_t* str)
58 {
59 return is_high_surrogate(str[0]) && is_low_surrogate(str[1]);
60 }
61
67 inline char32_t surrogate_pair_to_ucs4(_In_reads_(2) const utf16_t* str)
68 {
69 assert(is_surrogate_pair(str));
70 return
71 ((char32_t)(str[0] - 0xd800) << 10) +
72 (char32_t)(str[1] - 0xdc00) +
73 0x10000;
74 }
75
81 inline void ucs4_to_surrogate_pair(_Out_writes_(2) utf16_t* str, _In_ char32_t chr)
82 {
83 assert(chr >= 0x10000);
84 chr -= 0x10000;
85 str[0] = 0xd800 + (char32_t)((chr >> 10) & 0x3ff);
86 str[1] = 0xdc00 + (char32_t)(chr & 0x3ff);
87 }
88
94 inline bool iscombining(_In_ char32_t chr)
95 {
96 return
97 0x0300 <= chr && chr < 0x0370 ||
98 0x1dc0 <= chr && chr < 0x1e00 ||
99 0x20d0 <= chr && chr < 0x2100 ||
100 0xfe20 <= chr && chr < 0xfe30;
101 }
102
108 template <class T>
109 inline size_t islbreak(_In_ T chr)
110 {
111 return chr == '\n' || chr == '\r';
112 }
113
120 template <class T>
121 inline size_t islbreak(_In_reads_or_z_opt_(count) const T* chr, _In_ size_t count)
122 {
123 _Analysis_assume_(chr || !count);
124 if (count >= 2 && (chr[0] == '\r' && chr[1] == '\n' || chr[0] == '\n' && chr[1] == '\r'))
125 return 2;
126 if (count > 1 && (chr[0] == '\n' || chr[0] == '\r'))
127 return 1;
128 return 0;
129 }
130
137 inline size_t glyphlen(_In_reads_or_z_opt_(count) const wchar_t* glyph, _In_ size_t count)
138 {
139 _Analysis_assume_(glyph || !count);
140 if (count) {
141#ifdef _WIN32
142 size_t i = count < 2 || !is_surrogate_pair(glyph) ? 1 : 2;
143#else
144 size_t i = 1;
145#endif
146 for (; i < count && iscombining(glyph[i]); ++i);
147 return i;
148 }
149 return 0;
150 }
151
159 template <class T>
160 inline size_t strlen(_In_z_ const T* str)
161 {
162 assert(str);
163 size_t i;
164 for (i = 0; str[i]; ++i);
165 return i;
166 }
167
176 template <class T>
177 inline size_t strnlen(_In_reads_or_z_opt_(count) const T* str, _In_ size_t count)
178 {
179 assert(str);
180 size_t i;
181 for (i = 0; i < count && str[i]; ++i);
182 return i;
183 }
184
185 constexpr auto npos{ static_cast<size_t>(-1) };
186
196 template <class T>
197 inline size_t strnchr(
198 _In_reads_or_z_opt_(count) const T* str,
199 _In_ size_t count,
200 _In_ T chr)
201 {
202 assert(str || !count);
203 for (size_t i = 0; i < count && str[i]; ++i)
204 if (str[i] == chr) return i;
205 return npos;
206 }
207
217 template <class T>
218 inline size_t strrnchr(
219 _In_reads_or_z_opt_(count) const T* str,
220 _In_ size_t count,
221 _In_ T chr)
222 {
223 assert(str || !count);
224 size_t z = npos;
225 for (size_t i = 0; i < count && str[i]; ++i)
226 if (str[i] == chr) z = i;
227 return z;
228 }
229
239 template <class T>
240 inline size_t strnichr(
241 _In_reads_or_z_opt_(count) const T* str,
242 _In_ size_t count,
243 _In_ T chr,
244 _In_ const std::locale& locale)
245 {
246 assert(str || !count);
247 const auto& ctype = std::use_facet<std::ctype<T>>(locale);
248 chr = ctype.tolower(chr);
249 for (size_t i = 0; i < count && str[i]; ++i)
250 if (ctype.tolower(str[i]) == chr) return i;
251 return npos;
252 }
253
263 template <class T>
264 inline size_t strrnichr(
265 _In_reads_or_z_opt_(count) const T* str,
266 _In_ size_t count,
267 _In_ T chr,
268 _In_ const std::locale& locale)
269 {
270 assert(str || !count);
271 const auto& ctype = std::use_facet<std::ctype<T>>(locale);
272 chr = ctype.tolower(chr);
273 size_t z = npos;
274 for (size_t i = 0; i < count && str[i]; ++i)
275 if (ctype.tolower(str[i]) == chr) z = i;
276 return z;
277 }
278
289 template <class T1, class T2>
290 inline int strncmp(
291 _In_reads_or_z_opt_(count1) const T1* str1, _In_ size_t count1,
292 _In_reads_or_z_opt_(count2) const T2* str2, _In_ size_t count2)
293 {
294 assert(str1 || !count1);
295 assert(str2 || !count2);
296 size_t i; T1 a; T2 b;
297 for (i = 0; i < count1 && i < count2 && ((a = str1[i]) | (b = str2[i])); ++i) {
298 if (a > b) return +1;
299 if (a < b) return -1;
300 }
301 if (i < count1 && str1[i]) return +1;
302 if (i < count2 && str2[i]) return -1;
303 return 0;
304 }
305
316 template <class T>
317 inline int strncoll(
318 _In_reads_or_z_opt_(count1) const T* str1, _In_ size_t count1,
319 _In_reads_or_z_opt_(count2) const T* str2, _In_ size_t count2,
320 _In_ const std::locale& locale)
321 {
322 assert(str1 || !count1);
323 assert(str2 || !count2);
324 auto& collate = std::use_facet<std::collate<T>>(locale);
325 return collate.compare(str1, str1 + count1, str2, str2 + count2);
326 }
327
338 template <class T1, class T2>
339 inline int strnicmp(
340 _In_reads_or_z_opt_(count1) const T1* str1, _In_ size_t count1,
341 _In_reads_or_z_opt_(count2) const T2* str2, _In_ size_t count2,
342 _In_ const std::locale& locale)
343 {
344 assert(str1 || !count1);
345 assert(str2 || !count2);
346 size_t i; T1 a; T2 b;
347 const auto& ctype1 = std::use_facet<std::ctype<T1>>(locale);
348 const auto& ctype2 = std::use_facet<std::ctype<T2>>(locale);
349 for (i = 0; i < count1 && i < count2 && ((a = ctype1.tolower(str1[i])) | (b = ctype2.tolower(str2[i]))); i++) {
350 if (a > b) return +1;
351 if (a < b) return -1;
352 }
353 if (i < count1 && str1[i]) return +1;
354 if (i < count2 && str2[i]) return -1;
355 return 0;
356 }
357
367 template <class T1, class T2>
368 inline size_t strnstr(
369 _In_reads_or_z_opt_(count) const T1* str,
370 _In_ size_t count,
371 _In_z_ const T2* sample)
372 {
373 assert(str || !count);
374 assert(sample);
375 for (size_t offset = 0;; ++offset) {
376 for (size_t i = offset, j = 0;; ++i, ++j) {
377 if (!sample[j])
378 return offset;
379 if (i >= count || !str[i])
380 return npos;
381 if (str[i] != sample[j])
382 break;
383 }
384 }
385 }
386
396 template <class T1, class T2>
397 inline size_t strnistr(
398 _In_reads_or_z_opt_(count) const T1* str,
399 _In_ size_t count,
400 _In_z_ const T2* sample,
401 _In_ const std::locale& locale)
402 {
403 assert(str || !count);
404 assert(sample);
405 const auto& ctype1 = std::use_facet<std::ctype<T1>>(locale);
406 const auto& ctype2 = std::use_facet<std::ctype<T2>>(locale);
407 for (size_t offset = 0;; ++offset) {
408 for (size_t i = offset, j = 0;; ++i, ++j) {
409 if (!sample[j])
410 return offset;
411 if (i >= count || !str[i])
412 return npos;
413 if (ctype1.tolower(str[i]) != ctype2.tolower(sample[j]))
414 break;
415 }
416 }
417 }
418
427 template <class T1, class T2>
428 inline size_t strcpy(
429 _Out_writes_z_(_String_length_(src) + 1) T1* dst,
430 _In_z_ const T2* src)
431 {
432 assert(dst && src);
433 for (size_t i = 0; ; ++i) {
434 if ((dst[i] = src[i]) == 0)
435 return i;
436 }
437 }
438
448 template <class T1, class T2>
449 inline size_t strncpy(
450 _Out_writes_(count) _Post_maybez_ T1* dst,
451 _In_reads_or_z_opt_(count) const T2* src, _In_ size_t count)
452 {
453 assert(dst && src || !count);
454 for (size_t i = 0; ; ++i) {
455 if (i >= count)
456 return i;
457 if ((dst[i] = src[i]) == 0)
458 return i;
459 }
460 }
461
472 template <class T1, class T2>
473 inline size_t strncpy(
474 _Out_writes_(count_dst) _Post_maybez_ T1* dst, _In_ size_t count_dst,
475 _In_reads_or_z_opt_(count_src) const T2* src, _In_ size_t count_src)
476 {
477 assert(dst || !count_dst);
478 assert(src || !count_src);
479 for (size_t i = 0; ; ++i)
480 {
481 if (i > count_dst)
482 return i;
483 if (i > count_src) {
484 dst[i] = 0;
485 return i;
486 }
487 if ((dst[i] = src[i]) == 0)
488 return i;
489 }
490 }
491
500 template <class T1, class T2>
501 inline size_t strcat(
502 _In_z_ _Out_writes_z_(_String_length_(dst) + _String_length_(src) + 1) T1* dst,
503 _In_z_ const T2* src)
504 {
505 assert(dst && src);
506 for (size_t i = 0, j = stdex::strlen<T1>(dst); ; ++i, ++j) {
507 if ((dst[j] = src[i]) == 0)
508 return j;
509 }
510 }
511
521 template <class T1, class T2>
522 inline size_t strncat(
523 _Out_writes_(count) _Post_maybez_ T1* dst,
524 _In_reads_or_z_opt_(count) const T2* src, _In_ size_t count)
525 {
526 assert(dst && src || !count);
527 for (size_t i = 0, j = stdex::strlen<T1>(dst); ; ++i, ++j) {
528 if (i >= count)
529 return j;
530 if ((dst[j] = src[i]) == 0)
531 return j;
532 }
533 }
534
545 template <class T1, class T2>
546 inline size_t strncat(
547 _Out_writes_(count_dst) _Post_maybez_ T1* dst, _In_ size_t count_dst,
548 _In_reads_or_z_opt_(count_src) const T2* src, _In_ size_t count_src)
549 {
550 assert(dst || !count_dst);
551 assert(src || !count_src);
552 for (size_t i = 0, j = stdex::strnlen<T1>(dst, count_dst); ; ++i, ++j)
553 {
554 if (j > count_dst)
555 return j;
556 if (i > count_src) {
557 dst[j] = 0;
558 return j;
559 }
560 if ((dst[j] = src[i]) == 0)
561 return j;
562 }
563 }
564
574 template <class T>
575 inline size_t crlf2nl(_Out_writes_z_(strlen(src)) T* dst, _In_z_ const T* src)
576 {
577 assert(dst);
578 assert(src);
579 size_t i, j;
580 for (i = j = 0; src[j];) {
581 if (src[j] != '\r' || src[j + 1] != '\n')
582 dst[i++] = src[j++];
583 else {
584 dst[i++] = '\n';
585 j += 2;
586 }
587 }
588 dst[i] = 0;
589 return i;
590 }
591
593 template <class T, class T_bin>
594 inline T_bin strtoint(
595 _In_reads_or_z_opt_(count) const T* str, _In_ size_t count,
596 _Out_opt_ size_t* end,
597 _In_ int radix,
598 _Out_ uint8_t& flags)
599 {
600 assert(str || !count);
601 assert(radix == 0 || 2 <= radix && radix <= 36);
602
603 size_t i = 0;
604 T_bin value = 0, digit,
605 max_ui = (T_bin)-1,
606 max_ui_pre1, max_ui_pre2;
607
608 flags = 0;
609
610 // Skip leading spaces.
611 for (;; ++i) {
612 if (i >= count || !str[i]) goto error;
613 if (!isspace(str[i])) break;
614 }
615
616 // Read the sign.
617 if (str[i] == '+') {
618 flags &= ~0x01;
619 ++i;
620 if (i >= count || !str[i]) goto error;
621 }
622 else if (str[i] == '-') {
623 flags |= 0x01;
624 ++i;
625 if (i >= count || !str[i]) goto error;
626 }
627
628 if (radix == 16) {
629 // On hexadecimal, allow leading 0x.
630 if (str[i] == '0' && i + 1 < count && (str[i + 1] == 'x' || str[i + 1] == 'X')) {
631 i += 2;
632 if (i >= count || !str[i]) goto error;
633 }
634 }
635 else if (!radix) {
636 // Autodetect radix.
637 if (str[i] == '0') {
638 ++i;
639 if (i >= count || !str[i]) goto error;
640 if (str[i] == 'x' || str[i] == 'X') {
641 radix = 16;
642 ++i;
643 if (i >= count || !str[i]) goto error;
644 }
645 else
646 radix = 8;
647 }
648 else
649 radix = 10;
650 }
651
652 // We have the radix.
653 max_ui_pre1 = max_ui / (T_bin)radix;
654 max_ui_pre2 = max_ui % (T_bin)radix;
655 for (;;) {
656 if ('0' <= str[i] && str[i] <= '9')
657 digit = (T_bin)str[i] - '0';
658 else if ('A' <= str[i] && str[i] <= 'Z')
659 digit = (T_bin)str[i] - 'A' + '\x0a';
660 else if ('a' <= str[i] && str[i] <= 'z')
661 digit = (T_bin)str[i] - 'a' + '\x0a';
662 else
663 goto error;
664 if (digit >= (T_bin)radix)
665 goto error;
666
667 if (value < max_ui_pre1 || // Multiplication nor addition will not overflow.
668 value == max_ui_pre1 && digit <= max_ui_pre2) // Small digits will not overflow.
669 value = value * (T_bin)radix + digit;
670 else {
671 // Overflow!
672 flags |= 0x02;
673 }
674
675 ++i;
676 if (i >= count || !str[i])
677 goto error;
678 }
679
680 error:
681 if (end) *end = i;
682 return value;
683 }
685
696 template <class T, class T_bin>
697 T_bin strtoint(
698 _In_reads_or_z_opt_(count) const T* str, _In_ size_t count,
699 _Out_opt_ size_t* end,
700 _In_ int radix)
701 {
702 uint8_t flags;
703 T_bin value;
704
705 switch (sizeof(T_bin)) {
706 case 1:
707 value = (T_bin)strtoint<T, uint8_t>(str, count, end, radix, flags);
708 if ((flags & 0x01) && (value & 0x80)) {
709 // Sign bit is 1 => overflow.
710 flags |= 0x02;
711 }
712 return (flags & 0x02) ?
713 (flags & 0x01) ? (T_bin)0x80 : (T_bin)0x7f :
714 (flags & 0x01) ? -value : value;
715
716 case 2:
717 value = (T_bin)strtoint<T, uint16_t>(str, count, end, radix, flags);
718 if ((flags & 0x01) && (value & 0x8000)) {
719 // Sign bit is 1 => overflow.
720 flags |= 0x02;
721 }
722 return (flags & 0x02) ?
723 (flags & 0x01) ? (T_bin)0x8000 : (T_bin)0x7fff :
724 (flags & 0x01) ? -value : value;
725
726 case 4:
727 value = (T_bin)strtoint<T, uint32_t>(str, count, end, radix, flags);
728 if ((flags & 0x01) && (value & 0x80000000)) {
729 // Sign bit is 1 => overflow.
730 flags |= 0x02;
731 }
732 return (flags & 0x02) ?
733 (flags & 0x01) ? (T_bin)0x80000000 : (T_bin)0x7fffffff :
734 (flags & 0x01) ? -value : value;
735
736 case 8:
737 value = (T_bin)strtoint<T, uint64_t>(str, count, end, radix, flags);
738 if ((flags & 0x01) && (value & 0x8000000000000000)) {
739 // Sign bit is 1 => overflow.
740 flags |= 0x02;
741 }
742 return (flags & 0x02) ?
743 (flags & 0x01) ? (T_bin)0x8000000000000000 : (T_bin)0x7fffffffffffffff :
744 (flags & 0x01) ? -value : value;
745
746 default:
747 throw std::invalid_argument("Unsupported bit length");
748 }
749 }
750
761 template <class T, class T_bin>
762 inline T_bin strtouint(
763 _In_reads_or_z_opt_(count) const T* str,
764 _In_ size_t count,
765 _Out_opt_ size_t* end,
766 _In_ int radix)
767 {
768 uint8_t flags;
769 T_bin value;
770
771 switch (sizeof(T_bin)) {
772 case 1: value = (T_bin)strtoint<T, uint8_t>(str, count, end, radix, flags); break;
773 case 2: value = (T_bin)strtoint<T, uint16_t>(str, count, end, radix, flags); break;
774 case 4: value = (T_bin)strtoint<T, uint32_t>(str, count, end, radix, flags); break;
775 case 8: value = (T_bin)strtoint<T, uint64_t>(str, count, end, radix, flags); break;
776 default: throw std::invalid_argument("Unsupported bit length");
777 }
778
779 return (flags & 0x02) ?
780 (flags & 0x01) ? (T_bin)0 : (T_bin)-1 :
781 (flags & 0x01) ? ~value : value;
782 }
783
794 template <class T>
795 inline int32_t strto32(
796 _In_reads_or_z_opt_(count) const T* str, _In_ size_t count,
797 _Out_opt_ size_t* end,
798 _In_ int radix)
799 {
800 return strtoint<T, int32_t>(str, count, end, radix);
801 }
802
813 template <class T>
814 inline int64_t strto64(
815 _In_reads_or_z_opt_(count) const T* str, _In_ size_t count,
816 _Out_opt_ size_t* end,
817 _In_ int radix)
818 {
819 return strtoint<T, int64_t>(str, count, end, radix);
820 }
821
833 template <class T>
834 inline intptr_t strtoi(
835 _In_reads_or_z_opt_(count) const T* str, _In_ size_t count,
836 _Out_opt_ size_t* end,
837 _In_ int radix)
838 {
839#if defined(_WIN64) || defined(__LP64__)
840 return (intptr_t)strto64(str, count, end, radix);
841#else
842 return (intptr_t)strto32(str, count, end, radix);
843#endif
844 }
845
856 template <class T>
857 inline uint32_t strtou32(
858 _In_reads_or_z_opt_(count) const T* str, _In_ size_t count,
859 _Out_opt_ size_t* end,
860 _In_ int radix)
861 {
862 return strtouint<T, uint32_t>(str, count, end, radix);
863 }
864
875 template <class T>
876 inline uint64_t strtou64(
877 _In_reads_or_z_opt_(count) const T* str, _In_ size_t count,
878 _Out_opt_ size_t* end,
879 _In_ int radix)
880 {
881 return strtouint<T, uint64_t>(str, count, end, radix);
882 }
883
895 template <class T>
896 inline size_t strtoui(
897 _In_reads_or_z_opt_(count) const T* str, _In_ size_t count,
898 _Out_opt_ size_t* end,
899 _In_ int radix)
900 {
901#if defined(_WIN64) || defined(__LP64__)
902 return (size_t)strtou64(str, count, end, radix);
903#else
904 return (size_t)strtou32(str, count, end, radix);
905#endif
906 }
907
909 inline int vsnprintf(_Out_z_cap_(capacity) char *str, _In_ size_t capacity, _In_z_ _Printf_format_string_params_(2) const char *format, _In_opt_ locale_t locale, _In_ va_list arg)
910 {
911 int r;
912#ifdef _WIN32
913 // Don't use _vsnprintf_s(). It terminates the string even if we want to print to the edge of the buffer.
914#pragma warning(suppress: 4996)
915 r = _vsnprintf_l(str, capacity, format, locale, arg);
916#else
917 r = vsnprintf(str, capacity, format, arg);
918#endif
919 if (r == -1 && strnlen(str, capacity) == capacity) {
920 // Buffer overrun. Estimate buffer size for the next iteration.
921 capacity += std::max<size_t>(capacity / 8, 0x80);
922 if (capacity > INT_MAX)
923 throw std::invalid_argument("string too big");
924 return (int)capacity;
925 }
926 return r;
927 }
928
929 inline int vsnprintf(_Out_z_cap_(capacity) wchar_t *str, _In_ size_t capacity, _In_z_ _Printf_format_string_params_(2) const wchar_t *format, _In_opt_ locale_t locale, _In_ va_list arg)
930 {
931 int r;
932
933#ifdef _WIN32
934 // Don't use _vsnwprintf_s(). It terminates the string even if we want to print to the edge of the buffer.
935#pragma warning(suppress: 4996)
936 r = _vsnwprintf_l(str, capacity, format, locale, arg);
937#else
938 r = vswprintf(str, capacity, format, arg);
939#endif
940 if (r == -1 && strnlen(str, capacity) == capacity) {
941 // Buffer overrun. Estimate buffer size for the next iteration.
942 capacity += std::max<size_t>(capacity / 8, 0x80);
943 if (capacity > INT_MAX)
944 throw std::invalid_argument("string too big");
945 return (int)capacity;
946 }
947 return r;
948 }
950
959 template<class _Elem, class _Traits, class _Ax>
960 inline void vappendf(_Inout_ std::basic_string<_Elem, _Traits, _Ax> &str, _In_z_ _Printf_format_string_params_(2) const _Elem *format, _In_opt_ locale_t locale, _In_ va_list arg)
961 {
962 _Elem buf[1024/sizeof(_Elem)];
963
964 // Try with stack buffer first.
965 int count = vsnprintf(buf, _countof(buf) - 1, format, locale, arg);
966 if (count >= 0) {
967 // Copy from stack.
968 str.append(buf, count);
969 } else {
970 for (size_t capacity = 2*1024/sizeof(_Elem);; capacity *= 2) {
971 // Allocate on heap and retry.
972 auto buf_dyn = std::make_unique<_Elem[]>(capacity);
973 count = vsnprintf(buf_dyn.get(), capacity - 1, format, locale, arg);
974 if (count >= 0) {
975 str.append(buf_dyn.get(), count);
976 break;
977 }
978 }
979 }
980 }
981
989 template<class _Elem, class _Traits, class _Ax>
990 inline void appendf(_Inout_ std::basic_string<_Elem, _Traits, _Ax> &str, _In_z_ _Printf_format_string_params_(2) const _Elem *format, _In_opt_ locale_t locale, ...)
991 {
992 va_list arg;
993 va_start(arg, locale);
994 vappendf(str, format, locale, arg);
995 va_end(arg);
996 }
997
1006 template<class _Elem, class _Traits, class _Ax>
1007 inline void vsprintf(_Inout_ std::basic_string<_Elem, _Traits, _Ax> &str, _In_z_ _Printf_format_string_params_(2) const _Elem *format, _In_opt_ locale_t locale, _In_ va_list arg)
1008 {
1009 str.clear();
1010 vappendf(str, format, locale, arg);
1011 }
1012
1020 template<class _Elem, class _Traits, class _Ax>
1021 inline void sprintf(_Inout_ std::basic_string<_Elem, _Traits, _Ax> &str, _In_z_ _Printf_format_string_params_(2) const _Elem *format, _In_opt_ locale_t locale, ...)
1022 {
1023 va_list arg;
1024 va_start(arg, locale);
1025 vsprintf(str, format, locale, arg);
1026 va_end(arg);
1027 }
1028
1038 template<class _Elem, class _Traits = std::char_traits<_Elem>, class _Ax = std::allocator<_Elem>>
1039 inline std::basic_string<_Elem, _Traits, _Ax> vsprintf(_In_z_ _Printf_format_string_params_(2) const _Elem *format, _In_opt_ locale_t locale, _In_ va_list arg)
1040 {
1041 std::basic_string<_Elem, _Traits, _Ax> str;
1042 vappendf(str, format, locale, arg);
1043 return str;
1044 }
1045
1054 template<class _Elem, class _Traits = std::char_traits<_Elem>, class _Ax = std::allocator<_Elem>>
1055 inline std::basic_string<_Elem, _Traits, _Ax> sprintf(_In_z_ _Printf_format_string_params_(2) const _Elem *format, _In_opt_ locale_t locale, ...)
1056 {
1057 va_list arg;
1058 va_start(arg, locale);
1059 auto str = vsprintf(format, locale, arg);
1060 va_end(arg);
1061 return str;
1062 }
1063}