Last active
March 4, 2024 21:49
-
-
Save komiga/5790115 to your computer and use it in GitHub Desktop.
constexpr format string parsing. I am fully prepared to suffer the consequences.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <cstddef> | |
| #include <stdexcept> | |
| #include <utility> | |
| #include <string> | |
| #include <iomanip> | |
| #include <sstream> | |
| #include <iostream> | |
| using String=std::string; | |
| using StringStream=std::stringstream; | |
| template<typename T> | |
| constexpr T | |
| min_ce( | |
| T const x, | |
| T const y | |
| ) noexcept { | |
| return | |
| x < y | |
| ? x | |
| : y | |
| ; | |
| } | |
| enum : std::size_t { | |
| ELEMENTS_MAX = 16u, | |
| ELEMENT_LAST = ELEMENTS_MAX - 1, | |
| ELEMENT_FLAG_COUNT = 4u | |
| }; | |
| enum : char { | |
| ELEMENT_CHAR = '%' | |
| }; | |
| enum class ElementType : unsigned { | |
| end = 0u, | |
| esc, | |
| dec, | |
| hex, | |
| oct, | |
| flt, | |
| boo, | |
| ptr, | |
| str | |
| }; | |
| enum class ElementFlags : unsigned { | |
| show_base = 1 << 0, | |
| show_sign = 1 << 1, | |
| zero_padded = 1 << 2, | |
| left_align = 1 << 3, | |
| none = 0x0000, | |
| all | |
| =show_base | |
| |show_sign | |
| |zero_padded | |
| |left_align | |
| , | |
| permitted_end = none, | |
| permitted_esc = none, | |
| permitted_dec = all & ~show_base, | |
| permitted_hex = all & ~show_sign, | |
| permitted_oct = all & ~show_sign, | |
| permitted_flt = all & ~show_base, | |
| permitted_boo = left_align, | |
| permitted_ptr = all, | |
| permitted_str = left_align | |
| }; | |
| template< | |
| typename T, | |
| class = void | |
| > | |
| struct type_to_element { | |
| using cast=T; | |
| static constexpr bool valid = false; | |
| static constexpr bool | |
| type_matches( | |
| ElementType const type | |
| ) noexcept { | |
| return false; | |
| } | |
| }; | |
| // integral | |
| template< | |
| typename T | |
| > | |
| struct type_to_element< | |
| T, | |
| typename std::enable_if< | |
| !std::is_same< | |
| bool, | |
| typename std::remove_const< | |
| typename std::remove_reference<T>::type | |
| >::type | |
| >::value && | |
| std::is_integral< | |
| typename std::remove_reference<T>::type | |
| >::value | |
| >::type | |
| > { | |
| using cast=T; | |
| static constexpr bool valid = true; | |
| static constexpr bool | |
| type_matches( | |
| ElementType const type | |
| ) noexcept { | |
| return false | |
| || ElementType::dec == type | |
| || ElementType::hex == type | |
| || ElementType::oct == type | |
| ? true | |
| : false; | |
| } | |
| }; | |
| // floating-point | |
| template< | |
| typename T | |
| > | |
| struct type_to_element< | |
| T, | |
| typename std::enable_if< | |
| std::is_floating_point< | |
| typename std::remove_reference<T>::type | |
| >::value | |
| >::type | |
| > { | |
| using cast=T; | |
| static constexpr bool valid = true; | |
| static constexpr bool | |
| type_matches( | |
| ElementType const type | |
| ) noexcept { | |
| return ElementType::flt == type; | |
| } | |
| }; | |
| // boolean | |
| template< | |
| typename T | |
| > | |
| struct type_to_element< | |
| T, | |
| typename std::enable_if< | |
| std::is_same< | |
| bool, | |
| typename std::remove_const< | |
| typename std::remove_reference<T>::type | |
| >::type | |
| >::value | |
| >::type | |
| > { | |
| using cast=T; | |
| static constexpr bool valid = true; | |
| static constexpr bool | |
| type_matches( | |
| ElementType const type | |
| ) noexcept { | |
| return ElementType::boo == type; | |
| } | |
| }; | |
| // pointer | |
| template< | |
| typename T | |
| > | |
| struct type_to_element< | |
| T, | |
| typename std::enable_if< | |
| std::is_pointer< | |
| typename std::remove_reference<T>::type | |
| >::value || | |
| std::is_same< // really? | |
| decltype(nullptr), | |
| typename std::remove_reference<T>::type | |
| >::value | |
| >::type | |
| > { | |
| using cast=void*; | |
| static constexpr bool valid = true; | |
| static constexpr bool | |
| type_matches( | |
| ElementType const type | |
| ) noexcept { | |
| return ElementType::ptr == type; | |
| } | |
| }; | |
| // string/object | |
| template< | |
| typename T | |
| > | |
| struct type_to_element< | |
| T, | |
| typename std::enable_if< | |
| // le hack | |
| !std::is_integral< | |
| typename std::remove_reference<T>::type | |
| >::value && | |
| !std::is_floating_point< | |
| typename std::remove_reference<T>::type | |
| >::value && ( | |
| ( | |
| std::is_array<typename std::remove_reference<T>::type>::value && | |
| 1u == std::rank<typename std::remove_reference<T>::type>::value && | |
| std::is_same< | |
| char, | |
| typename std::remove_extent< | |
| typename std::remove_reference<T>::type | |
| >::type | |
| >::value | |
| ) || | |
| std::is_same< | |
| char const*, | |
| typename std::remove_const< | |
| typename std::remove_reference<T>::type | |
| >::type | |
| >::value || | |
| std::is_same< | |
| std::ostream&, | |
| decltype(std::declval<std::ostream&>() << std::declval<T&>()) | |
| >::value | |
| )>::type | |
| > { | |
| using cast=T; | |
| static constexpr bool valid = true; | |
| static constexpr bool | |
| type_matches( | |
| ElementType const type | |
| ) noexcept { | |
| return ElementType::str == type; | |
| } | |
| }; | |
| namespace { | |
| static constexpr unsigned const | |
| s_flags_permitted[]{ | |
| static_cast<unsigned>(ElementFlags::permitted_end), | |
| static_cast<unsigned>(ElementFlags::permitted_esc), | |
| static_cast<unsigned>(ElementFlags::permitted_dec), | |
| static_cast<unsigned>(ElementFlags::permitted_hex), | |
| static_cast<unsigned>(ElementFlags::permitted_oct), | |
| static_cast<unsigned>(ElementFlags::permitted_flt), | |
| static_cast<unsigned>(ElementFlags::permitted_boo), | |
| static_cast<unsigned>(ElementFlags::permitted_ptr), | |
| static_cast<unsigned>(ElementFlags::permitted_str) | |
| }; | |
| static constexpr char const | |
| s_type_values[]{ | |
| '\0', | |
| ELEMENT_CHAR, | |
| 'd', | |
| 'x', | |
| 'o', | |
| 'f', | |
| 'b', | |
| 'p', | |
| 's' | |
| }, | |
| s_type_name_invalid[] = "INVALID", | |
| * const s_type_names[]{ | |
| "end", | |
| "esc", | |
| "dec", | |
| "hex", | |
| "oct", | |
| "flt", | |
| "boo", | |
| "ptr", | |
| "str" | |
| }; | |
| } // anonymous namespace | |
| constexpr char const* const | |
| get_element_type_name( | |
| ElementType const type | |
| ) noexcept { | |
| return | |
| std::extent<decltype(s_type_names)>::value > static_cast<unsigned>(type) | |
| ? s_type_names[static_cast<unsigned>(type)] | |
| : s_type_name_invalid | |
| ; | |
| } | |
| constexpr std::size_t | |
| element_flag_count( | |
| unsigned const flags, | |
| std::size_t const count = 0u, | |
| std::size_t const index = 0u | |
| ) { | |
| return | |
| index < ELEMENT_FLAG_COUNT | |
| ? element_flag_count( | |
| flags, | |
| count + (0 != (flags & (1 << index))), | |
| index + 1u | |
| ) | |
| : count | |
| ; | |
| } | |
| enum class ParticleKind : unsigned { | |
| invalid = 0u, | |
| type, | |
| flag | |
| }; | |
| struct Particle { | |
| ParticleKind const kind; | |
| char const value; | |
| union { | |
| ElementType const type; | |
| unsigned const flag; | |
| }; | |
| enum invalid_t { | |
| invalid | |
| }; | |
| constexpr | |
| Particle( | |
| enum invalid_t const | |
| ) noexcept | |
| : kind(ParticleKind::invalid) | |
| , value('\0') | |
| , flag(0u) | |
| {} | |
| constexpr | |
| Particle( | |
| char const value, | |
| ElementType const type | |
| ) noexcept | |
| : kind(ParticleKind::type) | |
| , value(value) | |
| , type(type) | |
| {} | |
| constexpr | |
| Particle( | |
| char const value, | |
| ElementFlags const flag | |
| ) noexcept | |
| : kind(ParticleKind::flag) | |
| , value(value) | |
| , flag(static_cast<unsigned>(flag)) | |
| {} | |
| }; | |
| constexpr Particle const | |
| s_particles[]{ | |
| {ELEMENT_CHAR, ElementType::esc}, | |
| {'d', ElementType::dec}, | |
| {'x', ElementType::hex}, | |
| {'o', ElementType::oct}, | |
| {'f', ElementType::flt}, | |
| {'b', ElementType::boo}, | |
| {'p', ElementType::ptr}, | |
| {'s', ElementType::str}, | |
| {'#', ElementFlags::show_base}, | |
| {'+', ElementFlags::show_sign}, | |
| {'0', ElementFlags::zero_padded}, | |
| {'-', ElementFlags::left_align}, | |
| {Particle::invalid} | |
| }; | |
| constexpr Particle const& | |
| particle_classify( | |
| char const value, | |
| std::size_t const index = 0u | |
| ) noexcept { | |
| return false | |
| || ParticleKind::invalid == s_particles[index].kind | |
| || value == s_particles[index].value | |
| ? s_particles[index] | |
| : particle_classify(value, index + 1u) | |
| ; | |
| } | |
| struct Format; | |
| struct Element; | |
| struct Element { | |
| public: | |
| Format const& fmt; | |
| std::size_t const idx; | |
| std::size_t const beg; | |
| ElementType const type; | |
| unsigned const flags; | |
| std::size_t const end; | |
| std::size_t const width; | |
| enum invalid_t { | |
| invalid | |
| }; | |
| private: | |
| bool _valid; | |
| constexpr std::size_t | |
| cons_beg( | |
| std::size_t const pos | |
| ) const noexcept; | |
| constexpr unsigned | |
| cons_flags_inner( | |
| Particle const& particle, | |
| std::size_t const next, | |
| unsigned const c_flags | |
| ) const noexcept; | |
| constexpr unsigned | |
| cons_flags( | |
| std::size_t const pos, | |
| unsigned const c_flags | |
| ) const noexcept; | |
| constexpr ElementType | |
| cons_type_inner( | |
| Particle const& particle, | |
| std::size_t const next | |
| ) const noexcept; | |
| constexpr ElementType | |
| cons_type( | |
| std::size_t const pos | |
| ) const noexcept; | |
| constexpr std::size_t | |
| cons_end() const noexcept; | |
| constexpr bool | |
| flag_check() const noexcept; | |
| constexpr bool | |
| valid_check() const noexcept; | |
| public: | |
| constexpr | |
| Element( | |
| Format const& fmt, | |
| std::size_t const index, | |
| enum invalid_t const | |
| ) noexcept; | |
| constexpr | |
| Element( | |
| Format const& fmt, | |
| std::size_t const index, | |
| std::size_t const pos | |
| ) noexcept; | |
| }; | |
| struct Format { | |
| public: | |
| char const* const string; | |
| std::size_t const size; | |
| Element const elements[ELEMENTS_MAX + 1u]; | |
| std::size_t const element_count; | |
| std::size_t const serial_count; | |
| private: | |
| constexpr Element | |
| e( | |
| std::size_t const index | |
| ) const noexcept { | |
| return | |
| 0u < index && ElementType::end == this->elements[index - 1].type | |
| ? Element{*this, index, Element::invalid} | |
| : Element{ | |
| *this, | |
| index, | |
| (0u == index) ? 0u : elements[index - 1].end | |
| } | |
| ; | |
| } | |
| constexpr std::size_t | |
| cons_count( | |
| std::size_t const index = 0u | |
| ) const noexcept { | |
| return false ? 0u | |
| : ELEMENTS_MAX + 1u == index | |
| ? throw std::logic_error("element array overrun") | |
| : ElementType::end == this->elements[index].type | |
| ? index | |
| : cons_count(index + 1u) | |
| ; | |
| } | |
| constexpr std::size_t | |
| cons_count_serial( | |
| std::size_t const count = 0u, | |
| std::size_t const index = 0u | |
| ) const noexcept { | |
| return false ? 0u | |
| : ELEMENTS_MAX + 1u == index | |
| ? throw std::logic_error("element array overrun") | |
| : ElementType::end == this->elements[index].type | |
| ? count | |
| : cons_count_serial( | |
| count | |
| + static_cast<std::size_t>( | |
| ElementType::esc != this->elements[index].type | |
| ), | |
| index + 1u | |
| ) | |
| ; | |
| } | |
| public: | |
| template< | |
| std::size_t N | |
| > | |
| constexpr explicit | |
| Format( | |
| char const (&string)[N] | |
| ) noexcept | |
| : string(string) | |
| , size(N) | |
| , elements{ | |
| e(0u ),e(1u ), | |
| e(2u ),e(3u ), | |
| e(4u ),e(5u ), | |
| e(6u ),e(7u ), | |
| e(8u ),e(9u ), | |
| e(10u),e(11u), | |
| e(12u),e(13u), | |
| e(14u),e(15u), | |
| e(16u) | |
| } | |
| , element_count(cons_count()) | |
| , serial_count(cons_count_serial()) | |
| {} | |
| constexpr Element const* | |
| begin() const noexcept { return elements; } | |
| constexpr Element const* | |
| end() const noexcept { return elements+ELEMENTS_MAX; } | |
| constexpr std::size_t | |
| next_serial_index( | |
| std::size_t const index | |
| ) const noexcept { | |
| return | |
| ElementType::esc == this->elements[index + 1u].type | |
| ? next_serial_index(index + 1u) | |
| : index + 1u | |
| ; | |
| } | |
| }; | |
| // beg | |
| constexpr std::size_t | |
| Element::cons_beg( | |
| std::size_t const pos | |
| ) const noexcept { | |
| return false ? 0u | |
| : pos >= this->fmt.size | |
| ? this->fmt.size | |
| : ELEMENT_CHAR == this->fmt.string[pos] | |
| ? pos | |
| : cons_beg(pos + 1u); | |
| } | |
| // type | |
| constexpr ElementType | |
| Element::cons_type_inner( | |
| Particle const& particle, | |
| std::size_t const next | |
| ) const noexcept { | |
| return false ? ElementType::end | |
| : ParticleKind::invalid == particle.kind | |
| ? throw std::logic_error("format string overflow; malformed element") | |
| : ParticleKind::flag == particle.kind | |
| ? cons_type(next) | |
| : particle.type // encountered ParticleKind::type | |
| ; | |
| } | |
| constexpr ElementType | |
| Element::cons_type( | |
| std::size_t const pos | |
| ) const noexcept { | |
| return false ? ElementType::end | |
| : this->beg == this->fmt.size | |
| ? ElementType::end // no ELEMENT_CHAR was found | |
| : pos >= this->fmt.size | |
| ? throw std::logic_error("format string overflow; malformed element") | |
| : cons_type_inner( | |
| particle_classify(this->fmt.string[pos]), | |
| pos + 1u | |
| ) | |
| ; | |
| } | |
| // flags | |
| constexpr unsigned | |
| Element::cons_flags_inner( | |
| Particle const& particle, | |
| std::size_t const next, | |
| unsigned const c_flags | |
| ) const noexcept { | |
| return false ? 0u | |
| : ParticleKind::invalid == particle.kind | |
| ? throw std::logic_error("format string overflow; malformed element") | |
| : ParticleKind::flag == particle.kind | |
| ? cons_flags(next, c_flags | particle.flag) | |
| : c_flags // encountered ParticleKind::type | |
| ; | |
| } | |
| constexpr unsigned | |
| Element::cons_flags( | |
| std::size_t const pos, | |
| unsigned const c_flags | |
| ) const noexcept { | |
| return false ? 0u | |
| : ElementType::end == this->type | |
| ? 0u | |
| : pos >= this->fmt.size | |
| ? throw std::logic_error("format string overflow; malformed element") | |
| : cons_flags_inner( | |
| particle_classify(this->fmt.string[pos]), | |
| pos + 1u, | |
| c_flags | |
| ) | |
| ; | |
| } | |
| // end | |
| constexpr std::size_t | |
| Element::cons_end() const noexcept { | |
| return | |
| ElementType::end == this->type | |
| ? this->fmt.size | |
| : min_ce( | |
| this->fmt.size, | |
| this->beg + element_flag_count(this->flags) + 2u | |
| ) | |
| ; | |
| } | |
| // _valid | |
| constexpr bool | |
| Element::flag_check() const noexcept { | |
| return | |
| 0u == (~s_flags_permitted[static_cast<unsigned>(this->type)] & this->flags); | |
| } | |
| constexpr bool | |
| Element::valid_check() const noexcept { | |
| return false ? false | |
| : ELEMENTS_MAX == this->idx && ElementType::end != this->type | |
| ? throw std::logic_error("not enough space for elements in format string") | |
| : !flag_check() | |
| ? throw std::logic_error("format flag(s) not valid with type") | |
| : true; | |
| } | |
| constexpr | |
| Element::Element( | |
| Format const& fmt, | |
| std::size_t const index, | |
| enum invalid_t const | |
| ) noexcept | |
| : fmt(fmt) | |
| , idx(index) | |
| , beg(fmt.size) | |
| , type(ElementType::end) | |
| , flags(0u) | |
| , end(fmt.size) | |
| , width(0u) | |
| , _valid(false) | |
| {} | |
| constexpr | |
| Element::Element( | |
| Format const& fmt, | |
| std::size_t const index, | |
| std::size_t const pos | |
| ) noexcept | |
| : fmt(fmt) | |
| , idx(index) | |
| , beg(cons_beg(pos)) | |
| , type(cons_type(this->beg + 1u)) | |
| , flags(cons_flags(this->beg + 1u, 0u)) | |
| , end(cons_end()) | |
| , width(0u) | |
| , _valid(valid_check()) | |
| {} | |
| std::ostream& | |
| operator<<( | |
| std::ostream& stream, | |
| Element const& e | |
| ) { | |
| stream | |
| << '{' | |
| << "idx=" << e.idx << ", " | |
| << "beg=" << e.beg << ", " | |
| << "end=" << e.end << ", " | |
| << "type=" << get_element_type_name(e.type) | |
| ; | |
| if (ElementType::end != e.type) { | |
| stream << ", blob=\""; | |
| stream.write( | |
| e.fmt.string + e.beg, | |
| static_cast<std::streamsize>(e.end - e.beg) | |
| ); | |
| stream << "\"}"; | |
| } else { | |
| stream << '}'; | |
| } | |
| return stream; | |
| } | |
| std::ostream& | |
| operator<<( | |
| std::ostream& stream, | |
| Format const& f | |
| ) { | |
| stream | |
| << "Format {\n" | |
| << " string=\"" << f.string << "\",\n" | |
| << " size=" << f.size << ",\n" | |
| << " element_count=" << f.element_count << ",\n" | |
| << " serial_count=" << f.serial_count << ",\n" | |
| << " elements: {\n" | |
| ; | |
| for ( | |
| std::size_t index = 0u; | |
| ELEMENTS_MAX > index; | |
| ++index | |
| ) { | |
| std::cout << " " << f.elements[index] << '\n'; | |
| } | |
| stream << " }\n}"; | |
| return stream; | |
| } | |
| // format | |
| namespace { | |
| template<Format const&, typename...> | |
| struct format_type_check_impl; | |
| template< | |
| Format const& format | |
| > | |
| struct format_type_check_impl<format> { | |
| static constexpr bool | |
| g( | |
| std::size_t const | |
| ) noexcept { | |
| return true; | |
| } | |
| }; | |
| template< | |
| Format const& format, | |
| typename I, | |
| typename... P | |
| > | |
| struct format_type_check_impl<format, I, P...> { | |
| static constexpr bool | |
| g( | |
| std::size_t const index | |
| ) noexcept { | |
| return false | |
| || !type_to_element<I>::valid | |
| || !type_to_element<I>::type_matches(format.elements[index].type) | |
| ? throw std::logic_error("type of argument does not match element") | |
| : format_type_check_impl<format, P...>::g( | |
| format.next_serial_index(index) | |
| ); | |
| } | |
| }; | |
| } // anonymous namespace | |
| template< | |
| Format const& format, | |
| typename... ArgP | |
| > | |
| constexpr bool | |
| format_type_check() noexcept { | |
| return format_type_check_impl<format, ArgP...>::g( | |
| ElementType::esc == format.elements[0u].type | |
| ? format.next_serial_index(0u) | |
| : 0u | |
| ); | |
| } | |
| namespace { | |
| template< | |
| Format const& format, | |
| typename Arg | |
| > | |
| inline void | |
| write_element( | |
| std::ostream& stream, | |
| Element const& element, | |
| Arg&& arg | |
| ) { | |
| using ios = std::ios_base; | |
| ios::fmtflags stream_flags | |
| = ios::showpoint | |
| | ios::skipws | |
| | ios::internal | |
| ; | |
| switch (element.type) { | |
| case ElementType::dec: | |
| stream_flags |= ios::dec; | |
| break; | |
| case ElementType::hex: | |
| stream_flags |= ios::hex; | |
| break; | |
| case ElementType::oct: | |
| stream_flags |= ios::oct; | |
| break; | |
| case ElementType::flt: | |
| stream_flags |= ios::fixed; | |
| break; | |
| case ElementType::boo: | |
| stream_flags |= ios::boolalpha; | |
| break; | |
| case ElementType::ptr: | |
| stream_flags |= ios::hex; | |
| // TODO: OK default? | |
| stream.width(sizeof(void*) << 1u); | |
| break; | |
| case ElementType::str: | |
| break; | |
| case ElementType::end: | |
| case ElementType::esc: | |
| break; | |
| } | |
| if (static_cast<unsigned>(ElementFlags::show_base) & element.flags) { | |
| stream_flags |= ios::showbase; | |
| } | |
| if (static_cast<unsigned>(ElementFlags::show_sign) & element.flags) { | |
| stream_flags |= ios::showpos; | |
| } | |
| if (static_cast<unsigned>(ElementFlags::zero_padded) & element.flags) { | |
| stream.fill('0'); | |
| } else { | |
| stream.fill(' '); | |
| } | |
| if (static_cast<unsigned>(ElementFlags::left_align) & element.flags) { | |
| stream_flags |= ios::left; | |
| } | |
| if (0u != element.width) { | |
| stream.width(static_cast<std::streamsize>(element.width)); | |
| } | |
| stream.setf( | |
| stream_flags, | |
| ios::internal | |
| | ios::skipws | |
| | ios::showpos | |
| | ios::showbase | |
| | ios::showpoint | |
| | ios::basefield | |
| | ios::floatfield | |
| | ios::adjustfield | |
| | ios::fixed | |
| | ios::boolalpha | |
| ); | |
| stream << | |
| static_cast<typename type_to_element<Arg>::cast>( | |
| std::forward<Arg>(arg) | |
| ) | |
| ; | |
| stream.unsetf(stream_flags); | |
| if (0u != element.width) { | |
| stream.width(0u); | |
| } | |
| } | |
| template< | |
| Format const& format | |
| > | |
| inline void | |
| stream_format_impl( | |
| std::ostream& stream, | |
| std::size_t const last_pos, | |
| std::size_t const element_index | |
| ) { | |
| Element const& element = format.elements[element_index]; | |
| stream.write( | |
| format.string + last_pos, | |
| static_cast<std::streamsize>(element.beg - last_pos) | |
| + (ElementType::esc == element.type) | |
| ); | |
| if (ElementType::end != element.type) { | |
| stream_format_impl<format>( | |
| stream, | |
| element.end, | |
| element_index + 1u | |
| ); | |
| } | |
| } | |
| template< | |
| Format const& format, | |
| typename ArgF, | |
| typename... ArgP | |
| > | |
| inline void | |
| stream_format_impl( | |
| std::ostream& stream, | |
| std::size_t const last_pos, | |
| std::size_t const element_index, | |
| ArgF&& front, | |
| ArgP&&... args | |
| ) { | |
| Element const& element = format.elements[element_index]; | |
| stream.write( | |
| format.string + last_pos, | |
| static_cast<std::streamsize>(element.beg - last_pos) | |
| + (ElementType::esc == element.type) | |
| ); | |
| if (ElementType::esc == element.type) { | |
| stream_format_impl<format>( | |
| stream, | |
| element.end, | |
| element_index + 1u, | |
| std::forward<ArgF>(front), | |
| std::forward<ArgP>(args)... | |
| ); | |
| } else { | |
| write_element<format>( | |
| stream, | |
| element, | |
| std::forward<ArgF>(front) | |
| ); | |
| stream_format_impl<format>( | |
| stream, | |
| element.end, | |
| element_index + 1u, | |
| std::forward<ArgP>(args)... | |
| ); | |
| } | |
| } | |
| } // anonymous namespace | |
| template< | |
| Format const& format, | |
| typename... ArgP | |
| > | |
| void | |
| stream_format( | |
| std::ostream& stream, | |
| ArgP&&... args | |
| ) { | |
| static_assert( | |
| sizeof...(ArgP) == format.serial_count, | |
| "arguments do not match format" | |
| ); | |
| static_assert( | |
| format_type_check<format, ArgP...>(), | |
| "type of argument does not match element in format" | |
| ); | |
| stream_format_impl<format>( | |
| stream, | |
| 0u, | |
| 0u, | |
| std::forward<ArgP>(args)... | |
| ); | |
| } | |
| template< | |
| Format const& format, | |
| typename... ArgP | |
| > | |
| String | |
| string_format( | |
| ArgP&&... args | |
| ) { | |
| StringStream stream; | |
| stream_format<format>(stream, std::forward<ArgP>(args)...); | |
| return stream.str(); | |
| } | |
| namespace { | |
| static constexpr Format const | |
| //bad_length{"%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"}, | |
| //bad_flag_1{"%-%"}, | |
| //bad_flag_2{"%+x"}, | |
| //bad_flag_3{"%#o"}, | |
| //bad_flag_4{"%#s"}, | |
| all{"%% %d %#x %#o %f %s"}, | |
| flags{"%d %#x %#o %f %b %#0p"}, | |
| max{"%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"}, | |
| obj{"%s"}, | |
| empty{"empty"}; | |
| } | |
| signed | |
| main() { | |
| std::cout | |
| << all << '\n' | |
| << flags << '\n' | |
| << max << '\n' | |
| << obj << '\n' | |
| << empty << '\n' | |
| ; | |
| std::cout | |
| << string_format<all>(3, 0x12abcdef, 0777, 3.14f, "string") << '\n' | |
| << string_format<flags>(42, 42, 42, 42.0f, true, nullptr) << '\n' | |
| << string_format<max>() << '\n' | |
| << string_format<obj>(obj.elements[0u]) << '\n' | |
| << string_format<empty>() << '\n' | |
| ; | |
| std::cout.flush(); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Oh my god