API Documentation
Scanning functions
Main part of the public API.
Generally, the functions in this group take a range, a format string, and a list of arguments. The arguments are parsed from the range based on the information given in the format string.
If the function takes a format string and a range, they must share
character types. Also, the format string must be convertible to
basic_string_view<CharT>
, where CharT
is that aforementioned
character type.
-
template<typename Range, typename Format, typename ...Args>
auto scn::scan(Range &&r, const Format &f, Args&... a) -> detail::scan_result_for_range<Range> The most fundamental part of the scanning API.
Reads from the range in
r
according to the format stringf
.int i; scn::scan("123", "{}", i); // i == 123
-
template<typename Range, typename ...Args>
auto scn::scan_default(Range &&r, Args&... a) -> detail::scan_result_for_range<Range> Equivalent to scan, but with a format string with the appropriate amount of space-separated
"{}"
s for the number of arguments.Because this function doesn’t have to parse the format string, performance is improved.
Adapted from the example for scan
int i; scn::scan_default("123", i); // i == 123
See also
-
template<typename Locale, typename Range, typename Format, typename ...Args>
auto scn::scan_localized(const Locale &loc, Range &&r, const Format &f, Args&... a) -> detail::scan_result_for_range<Range> Read from the range in
r
using the locale inloc
.loc
must be astd::locale
. The parameter is a template to avoid inclusion of<locale>
.Use of this function is discouraged, due to the overhead involved with locales. Note, that the other functions are completely locale-agnostic, and aren’t affected by changes to the global C locale.
double d; scn::scan_localized(std::locale{"fi_FI"}, "3,14", "{}", d); // d == 3.14
See also
-
template<typename T, typename Range>
auto scn::scan_value(Range &&r) -> detail::generic_scan_result_for_range<expected<T>, Range> Scans a single value with the default options, returning it instead of using an output parameter.
The parsed value is in
ret.value()
, ifret == true
. The return type of this function is otherwise similar to other scanning functions.auto ret = scn::scan_value<int>("42"); if (ret) { // ret.value() == 42 }
-
template<typename Format, typename ...Args, typename CharT = ranges::range_value_t<Format>>
auto scn::input(const Format &f, Args&... a) -> detail::scan_result_for_range<basic_file<CharT>&> Otherwise equivalent to scan, expect reads from
stdin
.Character type is determined by the format string. Syncs with
<cstdio>
.
-
template<typename CharT, typename Format, typename ...Args>
auto scn::prompt(const CharT *p, const Format &f, Args&... a) -> detail::scan_result_for_range<basic_file<CharT>&> Equivalent to input, except writes what’s in
p
tostdout
.int i{}; scn::prompt("What's your favorite number? ", "{}", i); // Equivalent to: // std::fputs("What's your favorite number? ", stdout); // scn::input("{}", i);
-
template<typename Range, typename String, typename Until>
auto scn::getline(Range &&r, String &str, Until until) -> detail::scan_result_for_range<Range> Read the range in
r
intostr
untiluntil
is found.until
will be skipped in parsing: it will not be pushed intostr
, and the returned range will go past it.If
str
is convertible to abasic_string_view
:And if
r
is acontiguous_range
:str
is set to point insider
with the appropriate length
if not, returns an error
Otherwise, clears
str
by callingstr.clear()
, and then reads the range intostr
as if by repeatedly callingstr.push_back
.str.reserve()
is also required to be present.Until
can either be the same asr
character type (char
orwchar_t
), orcode_point
.auto source = "hello\nworld" std::string line; auto result = scn::getline(source, line, '\n'); // line == "hello" // result.range() == "world" // Using the other overload result = scn::getline(result.range(), line); // line == "world" // result.empty() == true
-
template<typename Range, typename String, typename CharT = typename detail::extract_char_type<ranges::iterator_t<range_wrapper_for_t<Range>>>::type>
auto scn::getline(Range &&r, String &str) -> detail::scan_result_for_range<Range> Equivalent to getline with the last parameter set to
'\n'
with the appropriate character type.In other words, reads
r
intostr
until'\n'
is found.The character type is determined by
r
.
-
template<typename Range, typename Until>
auto scn::ignore_until(Range &&r, Until until) -> detail::scan_result_for_range<Range> Advances the beginning of
r
untiluntil
is found.
-
template<typename Range, typename Until>
auto scn::ignore_until_n(Range &&r, ranges::range_difference_t<Range> n, Until until) -> detail::scan_result_for_range<Range> Advances the beginning of
r
untiluntil
is found, or the beginning has been advancedn
times.Until
can be ther
character type (char
orwchar_t
), orcode_point
.
Source range
Various kinds of ranges can be passed to scanning functions.
Fundamentally, a range is something that has a beginning and an end.
Examples of ranges are a string literal, a C-style array, and a std::vector
.
All of these can be passed to std::begin
and std::end
, which then return an iterator to the range.
This notion of ranges was standardized in C++20 with the Ranges TS.
This library provides barebone support of this functionality.
Source range requirements
- Ranges passed to scanning functions must be:
bidirectional
default and move constructible
Using C++20 concepts:
template <typename Range>
concept scannable_range =
std::ranges::bidirectional_range<Range> &&
std::default_constructible<Range> &&
std::move_constructible<Range>;
A bidirectional range is a range, the iterator type of which is
bidirectional: http://eel.is/c++draft/iterator.concepts#iterator.concept.bidir.
Bidirectionality means, that the iterator can be moved both
forwards: ++it
and backwards --it
.
Note, that both random-access and contiguous ranges are refinements of bidirectional ranges, and can be passed to the library. In fact, the library implements various optimizations for contiguous ranges.
Recommended range requirements
In addition, to limit unnecessary copies and possible dynamic memory allocations,
the ranges should be passed as an lvalue, and/or be a view
: http://eel.is/c++draft/range.view.
A view
is a range
that is cheap to copy: think string_view
or span
.
Passing a non-view as an rvalue will work, but it may cause worse performance, especially with larger source ranges.
// okay: view
scn::scan(std::string_view{...}, ...);
// okay: lvalue
std::string source = ...
scn::scan(source, ...);
// worse performance: non-view + rvalue
scn::scan(std::string{...}, ...);
In order for the .reconstruct()
member function to compile in the result object,
the range must be a pair-reconstructible-range
as defined by https://wg21.link/p1664r1,
i.e. be constructible from an iterator and a sentinel.
If the source range is contiguous, and/or its value_type
is its character type,
various fast-path optimizations are enabled inside the library implementation.
Also, a string_view
can only be scanned from such a range.
Character type
The range has an associated character type.
This character type can be either char
or wchar_t
.
The character type is determined by the result of operator*
of the range
iterator. If dereferencing the iterator returns
char
orwchar_t
: the character type ischar
orwchar_t
, respectively
expected<char>
orexpected<wchar_t>
: the character type ischar
orwchar_t
, respectively
Note on string literals
Please note, that only string literals are ranges (const char(&)[N]
), not pointers to a constant character (const char*
).
This is because:
It’s impossible to differentiate if a
const char*
is a null-terminated string, a pointer to a singlechar
, or a pointer to an array ofchar
. For safety reasons,const char*
is thus not an allowed source range type.It’s how ranges in the standard are defined: a
const char*
cannot be passed tostd::ranges::begin
orstd::ranges::end
(it doesn’t have a clear beginning or an end, for the reason explained above), so it’s not even a range to begin with.
Therefore, this code is allowed, as it uses a string literal (const char(&)[N]
) as the source range type:
int i;
scn::scan_default("123", i);
But this code isn’t, as the source range type used is not a range, but a pointer to constant character (const char*
):
const char* source = "123";
int i;
scn::scan_default(source, i); // compiler error
This issue can be avoided by using a string_view
:
const char* source = "123";
int i;
scn::scan_default(scn::string_view{source}, i);
// std::string_view would also work
Range wrapper
-
template<typename Range>
class range_wrapper Wraps a source range for more consistent behavior.
Unnamed Group
-
template<typename R = range_nocvref_type, typename std::enable_if<SCN_CHECK_CONCEPT(ranges::sized_range<R>)>::type* = nullptr>
inline void advance_to(iterator it) noexcept Advance the begin iterator, until it’s equal to
it
.Assumes that
it
is reachable by repeatedly incrementing begin, will hang otherwise.
Public Functions
-
inline bool empty() const
Returns
true
ifbegin() == end()
.
-
inline iterator advance(difference_type n = 1) noexcept
Advance the begin iterator by
n
characters.
- inline iterator begin_underlying () const noexcept(noexcept(ranges::cbegin(SCN_DECLVAL(const range_nocvref_type &))))
Returns the begin iterator of the underlying source range, is not necessarily equal to
begin()
.
-
inline const range_type &range_underlying() const noexcept
Returns the underlying source range.
Note that
range_underlying().begin()
may not be equal tobegin()
.
-
template<typename R = range_nocvref_type, typename std::enable_if<SCN_CHECK_CONCEPT(ranges::contiguous_range<R>)>::type* = nullptr>
inline auto data() const noexcept(noexcept(*SCN_DECLVAL(ranges::iterator_t<const R>))) -> decltype(std::addressof(*SCN_DECLVAL(ranges::iterator_t<const R>))) Returns a pointer to the beginning of the range.
*this
must be contiguous.
-
template<typename R = range_nocvref_type, typename std::enable_if<SCN_CHECK_CONCEPT(ranges::sized_range<R>)>::type* = nullptr>
inline SCN_GCC_PUSH auto size() const noexcept(noexcept(ranges::distance(SCN_DECLVAL(ranges::iterator_t<const R>), SCN_DECLVAL(ranges::sentinel_t<const R>)))) -> decltype(ranges::distance(SCN_DECLVAL(ranges::iterator_t<const R>), SCN_DECLVAL(ranges::sentinel_t<const R>))) Returns
end() - begin()
.*this
must be sized.
-
inline error reset_to_rollback_point()
Reset
begin()
to the rollback point, as if by repeatedly callingoperator--()
on the begin iterator.Returns
error::unrecoverable_source_error
on failure.See also
-
inline void set_rollback_point()
Sets the rollback point equal to the current
begin()
iterator.See also
-
template<typename R>
inline auto reconstruct_and_rewrap() && -> range_wrapper<R> Construct a new source range from
begin()
andend()
, and wrap it in a newrange_wrapper
.
Public Static Attributes
-
static constexpr bool is_direct = is_direct_impl<range_nocvref_type>::value
true
ifvalue_type
is a character type (char
orwchar_t
)false
if it’s anexpected
containing a character
-
static constexpr bool is_contiguous = SCN_CHECK_CONCEPT(ranges::contiguous_range<range_nocvref_type>)
true
ifthis->data()
can be called, andmemcpy
can be performed on it.
-
static constexpr bool provides_buffer_access = provides_buffer_access_impl<range_nocvref_type>::value
true
if the range provides a way to access a contiguous buffer on it (detail::get_buffer()
), which may not provide the entire source data, e.g.a
span
ofspan
s (vectored I/O).
-
struct dummy
-
struct dummy2
-
template<typename R = range_nocvref_type, typename std::enable_if<SCN_CHECK_CONCEPT(ranges::sized_range<R>)>::type* = nullptr>
Return type
The return type of the scanning functions is based on the type of the given range. It contains an object of that range type, representing what was left over of the range after scanning. The type is designed in such a way as to minimize copying and dynamic memory allocations. The type also contains an error value.
-
struct wrapped_error
Base class for the result type returned by most scanning functions (except for scan_value).
scn::detail::scan_result_base inherits either from this class or expected.
-
template<typename WrappedRange, typename Base>
class scan_result_base : public scn::detail::scan_result_base_wrapper<Base> Type returned by scanning functions.
Contains an error (inherits from it: for error, that’s wrapped_error; with scan_value, inherits from expected), and the leftover range after scanning.
The leftover range may reference the range given to the scanning function. Please take the necessary measures to make sure that the original range outlives the leftover range. Alternatively, if possible for your specific range type, call the reconstruct() member function to get a new, independent range.
Subclassed by scn::detail::intermediary_scan_result< WrappedRange, Base >
Public Functions
-
inline iterator begin() const noexcept
Beginning of the leftover range.
-
inline sentinel end() const noexcept(noexcept(SCN_DECLVAL(wrapped_range_type).end()))
End of the leftover range.
-
inline bool empty() const noexcept(noexcept(SCN_DECLVAL(wrapped_range_type).end()))
Whether the leftover range is empty.
-
inline ranges::subrange<iterator, sentinel> subrange() const
A subrange pointing to the leftover range.
-
inline wrapped_range_type &range() &
Leftover range.
If the leftover range is used to scan a new value, this member function should be used.
See also
-
inline const wrapped_range_type &range() const &
Leftover range.
If the leftover range is used to scan a new value, this member function should be used.
See also
-
inline wrapped_range_type range() &&
Leftover range.
If the leftover range is used to scan a new value, this member function should be used.
See also
-
template<typename R = typename WrappedRange::range_type>
R reconstruct() const Reconstructs a range of the original type, independent of the leftover range, beginning from begin and ending in end.
Compiles only if range is reconstructible.
-
inline iterator begin() const noexcept
Note, that the values scanned are only touched iff the scanning succeeded, i.e. operator bool()
returns true
.
This means, that reading from a default-constructed value of a built-in type on error will cause UB:
int i;
auto ret = scn::scan("foo", "{}", i);
// ret == false
// i is still default-constructed -- reading from it is UB
Error types
-
class error
Error class.
Used as a return value for functions without a success value.
Public Types
-
enum code
Error code.
Values:
-
enumerator good
No error.
-
enumerator end_of_range
EOF.
-
enumerator invalid_format_string
Format string was invalid.
-
enumerator invalid_scanned_value
Scanned value was invalid for given type.
e.g. a period ‘.’ when scanning for an int
-
enumerator invalid_operation
Stream does not support the performed operation.
-
enumerator value_out_of_range
Scanned value was out of range for the desired type.
(e.g.
>2^32
for anuint32_t
)
-
enumerator invalid_argument
Invalid argument given to operation.
-
enumerator invalid_encoding
Source range has invalid (utf-8 or utf-16) encoding.
-
enumerator exceptions_required
This operation is only possible with exceptions enabled.
-
enumerator source_error
The source range emitted an error.
-
enumerator unrecoverable_source_error
The source range emitted an error that cannot be recovered from.
The stream is now unusable.
-
enumerator unrecoverable_internal_error
-
enumerator max_error
-
enumerator good
Public Functions
-
inline explicit constexpr operator bool() const noexcept
Evaluated to true if there was no error.
- inline SCN_NODISCARD constexpr enum code code () const noexcept
Get error code.
- inline SCN_NODISCARD constexpr bool is_recoverable () const noexcept
Returns
true
if, after this error, the state of the given input range is consistent, and thus, the range can be used for new scanning operations.
-
struct success_tag_t
-
enum code
Lists
-
template<typename Range, typename Container>
auto scn::scan_list(Range &&r, Container &c) -> detail::scan_result_for_range<Range> Reads values repeatedly from
r
and writes them intoc
.The values read are of type
Container::value_type
, and they are written intoc
usingc.push_back
. The values are separated by whitespace.The range is read, until:
c.max_size()
is reached, orrange
EOF
is reached
In these cases, an error will not be returned, and the beginning of the returned range will point to the first character after the scanned list.
If an invalid value is scanned,
error::invalid_scanned_value
is returned, but the values already invec
will remain there. The range is put back to the state it was before reading the invalid value.To scan into
span
, use span_list_wrapper. make_span_list_wrapperstd::vector<int> vec{}; auto result = scn::scan_list("123 456", vec); // vec == [123, 456] // result.empty() == true vec.clear(); result = scn::scan_list("123 456 abc", vec); // vec == [123, 456] // result.error() == invalid_scanned_value // result.range() == " abc"
- Parameters:
r – Range to read from
c – Container to write values to, using
c.push_back()
.Container::value_type
will be used to determine the type of the values to read.
-
template<typename Range, typename Container, typename CharT>
auto scn::scan_list_ex(Range &&r, Container &c, scan_list_options<CharT> options) -> detail::scan_result_for_range<Range> Otherwise equivalent to
scan_list()
, except can react to additional characters, based onoptions
.See
scan_list_options
for more information.std::vector<int> vec{}; auto result = scn::scan_list_ex("123, 456", vec, scn::list_separator(',')); // vec == [123, 456] // result.empty() == true
See also
See also
- Parameters:
r – Range to scan from
c – Container to write read values into
options – Options to use
-
template<typename Locale, typename Range, typename Container, typename CharT>
auto scn::scan_list_localized(const Locale &loc, Range &&r, Container &c, scan_list_options<CharT> options) -> detail::scan_result_for_range<Range> Otherwise equivalent to
scan_list_ex()
, except usesloc
to scan the values.See also
See also
- Parameters:
loc – Locale to use for scanning. Must be a
std::locale
.r – Range to scan from
c – Container to write read values into
options – Options to use
-
template<typename CharT>
struct scan_list_options Used to customize
scan_list_ex()
.list_separator
,list_until
andlist_separator_and_until
can be used to create a value of this type, taking advantage of template argument deduction (no need to hand-specifyCharT
).- Template Parameters:
CharT – Can be a code unit type (
char
orwchar_t
, depending on the source range), orcode_point
.
-
template<typename CharT>
scan_list_options<CharT> scn::list_separator(CharT ch) Create a
scan_list_options
forscan_list_ex
, by usingch
as the separator character.
-
template<typename CharT>
scan_list_options<CharT> scn::list_until(CharT ch) Create a
scan_list_options
forscan_list_ex
, by usingch
as the until-character.
-
template<typename CharT>
scan_list_options<CharT> scn::list_separator_and_until(CharT sep, CharT until) Create a
scan_list_options
forscan_list_ex
, by usingsep
as the separator, anduntil
as the until-character.
Convenience scan types
These types can be passed to scanning functions (scn::scan
and alike) as arguments, providing useful functionality.
-
template<typename T>
struct temporary Allows reading an rvalue.
Stores an rvalue and returns an lvalue reference to it via
operator()
. Create one with temp.
-
template<typename T, typename std::enable_if<!std::is_lvalue_reference<T>::value>::type* = nullptr>
temporary<T> scn::temp(T &&val) Factory function for temporary.
Canonical use case is with scn::span:
std::vector<char> buffer(32, '\0'); auto result = scn::scan("123", "{}", scn::temp(scn::make_span(buffer))); // buffer == "123"
-
template<typename T>
discard_type<T> &scn::discard() Scans an instance of
T
, but doesn’t store it anywhere.Uses
scn::temp
internally, so the user doesn’t have to bother.int i{}; // 123 is discarded, 456 is read into `i` auto result = scn::scan("123 456", "{} {}", scn::discard<T>(), i); // result == true // i == 456
-
template<typename T>
struct span_list_wrapper Adapts a
span
into a type that can be read into using scan_list.This way, potentially unnecessary dynamic memory allocations can be avoided. To use as a parameter to scan_list, use make_span_list_wrapper.
std::vector<int> buffer(8, 0); scn::span<int> s = scn::make_span(buffer); auto wrapper = scn::span_list_wrapper<int>(s); scn::scan_list("123 456", wrapper); // s[0] == buffer[0] == 123 // s[1] == buffer[1] == 456
See also
See also
-
template<typename T>
auto scn::make_span_list_wrapper(T &s) -> temporary<detail::span_list_wrapper_for<T>> Adapts a contiguous buffer into a type containing a
span
that can be read into using scn::scan_list.Example adapted from span_list_wrapper:
std::vector<int> buffer(8, 0); scn::scan_list("123 456", scn::make_span_list_wrapper(buffer)); // s[0] == buffer[0] == 123 // s[1] == buffer[1] == 456
See also
See also
Format string
Every value to be scanned from the source range is marked with a pair of
curly braces "{}"
in the format string. Inside these braces, additional
options can be specified. The syntax is not dissimilar from the one found in
fmtlib.
The information inside the braces consist of two parts: the index and the
scanning options, separated by a colon ':'
.
The index part can either be empty, or be an integer. If the index is specified for one of the arguments, it must be set for all of them. The index tells the library which argument the braces correspond to.
int i;
std::string str;
scn::scan(range, "{1} {0}", i, str);
// Reads from the range in the order of:
// string, whitespace, integer
// That's because the first format string braces have index '1', pointing to
// the second passed argument (indices start from 0), which is a string
After the index comes a colon and the scanning options. The colon only has to be there if any scanning options are specified.
The format of the format specifiers are as follows. [foo]
means an optional flag.
[[fill]align][width][L][type]
Fill and align
Values to be parsed can be aligned in the source, using fill characters.
For example, an integer could be aligned in a field using asterisks, like "****42****"
.
This can be parsed without extra trickery, by specifying the fill character *
, and center alignment ^
:
int i;
auto ret = scn::scan("*****42*****", "{:*^}", i);
// i == 42
// ret.empty() == true
The fill character can be any character other than }
.
If no fill character is given, a space is used.
Supported alignment options are:
<
: for left-alignment (value is in the left of the field, fill characters come after it)
>
: for right-alignment
^
: for center-alignment – fill characters both before and after the value.
Width
Width specifies the maximum number of characters (= code units) that can be read from the source stream. Width can be any unsigned integer.
std::string str{};
auto ret = scn::scan("abcde", "{:3}", str);
// str == "abc"
// ret.range() == "de"
Localized
Specifying the L
-flag will cause the library to use localized scanning for this value.
If a locale was passed to the scanning function (for example, with scn::scan_localized
), it will be used.
Otherwise, the global C++ locale will be used (std::locale{}
, set with std::locale::global()
).
double d{};
// uses global locale ("C", because std::locale::global() hasn't been called)
auto ret = scn::scan("3.14", "{:L}", d);
// ret.empty() == true
// d == 3.14
// uses the passed-in locale
ret = scn::scan_localized(std::locale{"fi_FI.UTF-8"}, "3,14", "{:L}", d);
// ret.empty() == true
// d == 3.14
Itself, the L
flag has an effect with floats, where it affects the accepted decimal separator.
In conjunction with other flags (n
and '
) it can have additional effects.
Type
The type flag can be used to further specify how the value is to be parsed, what values are accepted, and how they are interpreted. The accepted flags and their meanings depend on the actual type to be scanned.
Type: integral
For integral types the flags are organized in three categories. Up to one from each category can be present in the format string.
First category:
b
: Binary, optional prefix0b
or0B
accepted
Bnn
: (B
followed by two digits): custom base,n
in range 2-36 (inclusive), no base prefix accepted
c
: Read a code unit (do not skip preceding whitespace)
d
: Decimal, no base prefix accepted
i
: Integer, detect base by prefix:0b
/0B
for binary,0o
/0O
/0
for octal,0x
/0X
for hex, decimal otherwise
u
: Unsigned decimal, no sign or base prefix accepted (even for signed types)
o
: Octal, optional prefix0o
,0O
or0
accepted
x
: Hex, optional prefix0x
or0X
accepted(default):
d
if type is notchar
orwchar_t
,c
otherwise
Second category (if the first category was not c
):
n
: Accept digits as specified by the supplied locale, impliesL
(default): Only digits
[0-9]
are accepted, no custom digits
Third category (if the first category was not c
):
'
: Accept thousands separators: default to,
, use locale ifL
set(default): Only digits
[0-9]
are accepted, no thousands separator
Types considered ‘integral’, are the types specified by std::is_integral
, except for bool
, char8_t
, char16_t
, and char32_t
.
This includes signed and unsigned variants of char
, short
, int
, long
, long long
, and wchar_t
.
Type: float
For floats (float
, double
and long double
), there are also three categories,
where up to one from each category can be present in the format string.
First category:
a
andA
: Hex float accepted (e.g.0x1f.0p2
)
e
andE
: Scientific format accepted (123.456e2
)
f
andF
: Fixed-precision format accepted (123.456
)
g
andG
: General format (impliese
ANDf
)(default): Accept all (implies
e
ANDf
ANDa
)
Second category:
n
: Accept digits as specified by the supplied locale, impliesL
(default): Only digits
[0-9]
are accepted, no custom digits
Third category:
'
: Accept thousands separators: default to,
, use locale ifL
set(default): Only digits
[0-9]
are accepted, no thousands separator
Type: string
For strings (std::basic_string
, scn::/std::basic_string_view
, scn::span
), the supported options are as follows:
s
: Accept any non-whitespace characters (ifL
is set, use the supplied locale, otherwise usestd::isspace
with"C"
locale). Skips leading whitespace.
[
set]
: Accept any characters as specified by _set_, further information below. Does not skip leading whitespace.(default):
s
set can consist of literal characters ([abc]
only accepts a
, b
, and c
),
ranges of literal characters ([a-z]
only accepts characters from a
to z
),
or specifiers, that are detailed in the table below.
Literals can also be specified as hex values:
\xnn
:\x
followed by two hexadecimal digits -> hex value
\unnnn
:\u
followed by four hexadecimal digits -> Unicode code point
\Unnnnnnnn
:\U
followed by eight hexadecimal digits -> Unicode code point (max value0x10FFFF
)
Specifier |
Description |
Accepted characters |
---|---|---|
|
All characters |
|
|
Alphanumeric characters |
|
|
Letters |
|
|
Blank characters |
|
|
Control characters |
|
|
Digits |
|
|
“Graphic” characters |
|
|
Lowercase letters |
|
|
Printable characters |
|
|
Punctuation characters |
|
|
Whitespace characters |
|
|
Uppercase letters |
|
|
Hexadecimal digits |
|
|
Letters |
|
|
Letters, numbers, and underscore |
|
|
Whitespace |
|
|
Digits |
|
\l
, \w
, \s
and \d
can be inverted with capitalization: \L
, \W
, \S
and \D
, respectively.
If the first character in the set is ^
, all options are inverted.
\:
, \]
, \\^
, \\
specify literal :
, ]
, ^
, and \
, respectively.
-
ranges accept any value numerically between the two ends,
e.g. [A-z]
accepts every ascii value between 0x41
and 0x7a
, including characters like [
, \
, and ]
.
[a-Z]
is an error, because the range end must be greater or equal to its beginning.
If the L
flag is used, the supplied locale is used.
If not, the <cctype>
detailed in the above table is used, with the "C"
locale.
Format string |
Accepted characters |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NOT |
|
NOT |
|
|
(1): \n
means literal line break 0x0a
(2): Note the quadruple backslash: \\\\
is turned into [0x5c 0x5c]
(two backslash characters) in the actual string by C++,
which, in turn, is interpreted as an escaped backslash by scnlib.
Just a double backslash \\]
would lead to the closing square parenthesis being escaped, and the format string being invalid.
(3): Same as above: \\w
means the format string specifier \w
, literal \w
would be an invalid C++ escape sequence.
Type: bool
Any number of flags accepted
s
: Accept string values (true
andfalse
AND possible locale values, if usingL
)
i
: Accept int values (0
and1
)
n
:i
, except accepts localized digits, impliesL
(default):
s
+i
: Accept0
,1
,true
,false
, and possible locale string values if usingL
Type: code_point
Only flag c
accepted, does not affect behavior.
Note, code_point
, leading whitespace is not skipped.
Whitespace
Any amount of whitespace in the format string tells the library to skip until the next non-whitespace character is found from the range. Not finding any whitespace from the range is not an error.
Literal characters
To scan literal characters and immediately discard them, just write the
characters in the format string.
To read literal {
or }
, write {{
or }}
, respectively.
std::string bar;
scn::scan("foobar", "foo{}", bar);
// bar == "bar"
Semantics of scanning a value
In the beginning, with every scn::scan
(or similar) call, the library
wraps the given range in a scn::detail::range_wrapper
, using scn::wrap
.
This wrapper provides an uniform interface and lifetime semantics over all possible ranges.
The arguments to scan are wrapped in a scn::arg_store
.
These are then passed, alongside the format string, to scn::vscan
(or similar).
The appropriate context and parse context types are then constructed based on these values,
the format string, and the requested locale, and scn::visit
is called.
There, the library calls begin()
on the range, getting an iterator. This iterator is
advanced until a non-whitespace character is found.
After that, the format string is scanned character-by-character, until an
unescaped '{'
is found, after which the part after the '{'
is parsed,
until a ':'
or '}'
is found. If the parser finds an argument id,
the argument with that id is fetched from the argument list, otherwise the
next argument is used.
The parse()
member function of the appropriate scn::scanner
specialization is called, which parses the parsing options-part of the format
string argument, setting the member variables of the scn::scanner
specialization to their appropriate values.
After that, the scan()
member function is called. It reads the range,
starting from the aforementioned iterator, into a buffer until the next
whitespace character is found (except for char
/wchar_t
: just a single
character is read; and for span
: span.size()
characters are read). That
buffer is then parsed with the appropriate algorithm.
If some of the characters in the buffer were not used, these characters are
put back to the range, meaning that operator--
is called on the iterator.
Because how the range is read until a whitespace character, and how the
unused part of the buffer is simply put back to the range, some interesting
situations may arise. Please note, that the following behavior is consistent
with both scanf
and <iostream>
.
// chars do not skip leading whitespace by default
// strings do
char c;
std::string str;
// No whitespace character after first {}, no range whitespace is skipped
scn::scan("abc", "{}{}", c, str);
// c == 'a'
// str == "bc"
// Not finding whitespace to skip from the range when whitespace is found in
// the format string isn't an error
scn::scan("abc", "{} {}", c, str);
// c == 'a'
// str == "bc"
// string scanners skip leading whitespace
scn::scan("a bc", "{}{}", c, str);
// c == 'a'
// str == "bc"
// char scanners do not
scn::scan("ab c", "{}{}", str, c);
// str == "ab"
// c == ' '
// Nothing surprising
scn::scan("a bc", "{} {}", c, str);
// c == 'a'
// str == "bc"
Using scn::scan_default
is equivalent to using "{}"
in the format string
as many times as there are arguments, separated by whitespace.
scn::scan_default(range, a, b);
// Equivalent to:
// scn::scan(range, "{} {}", a, b);
Files
-
template<typename CharT>
class basic_file Range mapping to a C FILE*.
Not copyable or reconstructible.
Subclassed by scn::basic_owning_file< CharT >
Public Functions
-
inline basic_file(FILE *f)
Construct from a FILE*.
Must be a valid handle that can be read from.
-
inline FILE *handle() const
Get the FILE* for this range.
Only use this handle for reading sync() has been called and no reading operations have taken place after that.
See also
-
inline FILE *set_handle(FILE *f, bool allow_sync = true) noexcept
Reset the file handle.
Calls sync(), if necessary, before resetting.
- Returns:
The old handle
-
inline constexpr bool valid() const noexcept
Whether the file has been opened.
-
inline void sync() noexcept
Synchronizes this file with the underlying FILE*.
Invalidates all non-end iterators. File must be open.
Necessary for mixing-and-matching scnlib and <cstdio>:
scn::scan(file, ...); file.sync(); std::fscanf(file.handle(), ...);
Necessary for synchronizing result objects:
auto result = scn::scan(file, ...); // only result.range() can now be used for scanning result = scn::scan(result.range(), ...); // .sync() allows the original file to also be used file.sync(); result = scn::scan(file, ...);
-
class iterator
-
inline basic_file(FILE *f)
-
template<typename CharT>
class basic_owning_file : public scn::basic_file<CharT> A child class for basic_file, handling fopen, fclose, and lifetimes with RAII.
Public Functions
-
basic_owning_file() = default
Open an empty file.
-
inline basic_owning_file(const char *f, const char *mode)
Open a file, with fopen arguments.
-
inline explicit basic_owning_file(FILE *f)
Steal ownership of a FILE*.
-
inline bool open(const char *f, const char *mode)
fopen
-
inline bool open(FILE *f)
Steal ownership.
-
inline void close()
Close file.
- inline SCN_NODISCARD bool is_open () const
Is the file open.
-
basic_owning_file() = default
-
template<typename CharT>
class basic_mapped_file : public scn::detail::byte_mapped_file Memory-mapped file range.
Manages the lifetime of the mapping itself.
-
using scn::file = basic_file<char>
-
using scn::wfile = basic_file<wchar_t>
-
using scn::owning_file = basic_owning_file<char>
-
using scn::owning_wfile = basic_owning_file<wchar_t>
-
using scn::mapped_file = basic_mapped_file<char>
-
using scn::mapped_wfile = basic_mapped_file<wchar_t>
-
template<typename CharT>
basic_file<CharT> &scn::stdin_range() Get a reference to the global stdin range.
Lower level parsing and scanning operations
-
template<typename OriginalRange, typename Error = wrapped_error, typename WrappedRange>
auto scn::make_scan_result(vscan_result<WrappedRange> result) -> detail::scan_result_for_range<OriginalRange> template <typename Range, typename... Args> auto scan(Range&& r, string_view f, Args&... a) { auto range = scn::wrap(std::forward<Range>(r)); auto args = scn::make_args_for(range, f, a...); auto ret = scn::vscan(std::move(range), f, {args}); return scn::make_scan_result<Range>(std::move(ret)); }
- Template Parameters:
OriginalRange – The type of the range passed to the scanning function
- Parameters:
result – Return value of
vscan
- Returns:
Result object
-
template<typename WrappedRange, typename Format, typename ...Args, typename CharT = typename WrappedRange::char_type>
arg_store<CharT, Args...> scn::make_args_for(WrappedRange&, Format, Args&... args)
vscan
-
template<typename WrappedRange, typename CharT = typename WrappedRange::char_type>
vscan_result<WrappedRange> scn::vscan(WrappedRange range, basic_string_view<CharT> fmt, basic_args<CharT> &&args) In the spirit of {fmt}/
std::format
andvformat
,vscan
behaves similarly to scan, except instead of taking a variadic argument pack, it takes an object of typebasic_args
, which type-erases the arguments to scan.This, in effect, will decrease generated code size and compile times dramatically.
- Parameters:
range – Source range that has been wrapped with
detail::wrap
, and passed in as an rvalue.fmt – Format string to use
args – Type-erased values to read
-
template<typename WrappedRange, typename CharT = typename WrappedRange::char_type>
vscan_result<WrappedRange> scn::vscan_default(WrappedRange range, int n_args, basic_args<CharT> &&args) To be used with
scan_default
See also
- Parameters:
range – Source range that has been wrapped with
detail::wrap
, and passed in as an rvalue.n_args – Number of arguments in args
args – Type-erased values to read
-
template<typename WrappedRange, typename CharT = typename WrappedRange::char_type>
vscan_result<WrappedRange> scn::vscan_localized(WrappedRange range, basic_locale_ref<CharT> &&loc, basic_string_view<CharT> fmt, basic_args<CharT> &&args) To be used with
scan_localized
See also
- Parameters:
loc – Locale to use
range – Source range that has been wrapped with
detail::wrap
, and passed in as an rvalue.fmt – Format string to use
args – Type-erased values to read
Warning
doxygenstruct: Cannot find class “vscan_result” in doxygen xml output for project “scnlib” from directory: /home/docs/checkouts/readthedocs.org/user_builds/scnlib/checkouts/master/docs/build/xml
Low-level parsing
parse_integer
and parse_float
will provide super-fast parsing from a string, at the expense of some safety and usability guarantees.
Using these functions can easily lead to unexpected behavior or UB if not used correctly and proper precautions are not taken.
-
template<typename T, typename CharT>
expected<const CharT*> scn::parse_integer(basic_string_view<CharT> str, T &val, int base = 10) Parses an integer into
val
in basebase
fromstr
.Returns a pointer past the last character read, or an error.
- Parameters:
str – source, can’t be empty, cannot have:
preceding whitespace
preceding
"0x"
or"0"
(base is determined by thebase
parameter)'+'
sign ('-'
is fine)
val – parsed integer, must be value-constructed
base – between [2,36]
Scanner
Values are eventually scanned using a scn::scanner
.
-
struct parser_base
Base class for all scanners.
User-defined scanner must derive from this type.
Subclassed by scn::common_parser, scn::empty_parser
Public Static Functions
-
static inline constexpr bool skip_preceding_whitespace()
Returns
true
ifskip_range_whitespace()
is to be called before scanning this value.Defaults to
true
. Isfalse
for chars, code points and strings when using set scanning.
-
static inline constexpr bool support_align_and_fill()
Returns
true
if this scanner supports parsing align and fill specifiers from the format string, and then scanning them.Defaults to
false
,true
for all scnlib-defined scanners.
-
static inline constexpr bool skip_preceding_whitespace()
-
struct empty_parser : public scn::parser_base
A very simple parser base class, which only accepts empty format string specifiers, e.g.
{}
,{:}
or{1:}
.Subclassed by scn::scanner< T, typename std::enable_if< detail::is_std_streamable< char, T >::value||detail::is_std_streamable< wchar_t, T >::value >::type >
-
struct common_parser : public scn::parser_base
Provides a framework for building a format string parser.
Does not provide a
parse()
member function, so not a parser on to its own.Subclassed by scn::detail::float_scanner< double >, scn::detail::float_scanner< float >, scn::detail::float_scanner< long double >, scn::detail::integer_scanner< char >, scn::detail::integer_scanner< int >, scn::detail::integer_scanner< long >, scn::detail::integer_scanner< long long >, scn::detail::integer_scanner< short >, scn::detail::integer_scanner< signed char >, scn::detail::integer_scanner< unsigned char >, scn::detail::integer_scanner< unsigned int >, scn::detail::integer_scanner< unsigned long >, scn::detail::integer_scanner< unsigned long long >, scn::detail::integer_scanner< unsigned short >, scn::detail::integer_scanner< wchar_t >, scn::common_parser_default, scn::detail::bool_scanner, scn::detail::code_point_scanner, scn::detail::float_scanner< T >, scn::detail::integer_scanner< T >, scn::detail::string_scanner
Public Functions
-
template<typename ParseCtx, typename F, typename CharT = typename ParseCtx::char_type>
inline error parse_common(ParseCtx &pctx, span<const CharT> type_options, span<bool> type_flags, F &&type_cb) Parse a format string argument, using
parse_common_begin
,parse_common_flags
,parse_common_end
, and the supplied type flags.type_options.size() == type_flags.size()
must betrue
.pctx
must be valid, and must start at the format string argument specifiers, e.g. in the case of"{1:foo}"
->pctx == "foo}"
- Parameters:
pctx – Format string to parse
type_options – A span of characters, where each character corresponds to a valid type flag. For example, for characters, this span would be
[‘c’]
type_flags – A span of bools, where the values will be set to
true
, if a corresponding type flag fromtype_options
was found. Should be initialized to all-false
, as afalse
value will not be written.type_cb – A callback to call, if none of the
type_options
matched. Must have the signature(ParseCtx& pctx, bool& parsed) -> error
., whereparsed
is set totrue
, if the flag atpctx.next_char()
was parsed and advanced past.
-
template<typename ParseCtx>
inline error parse_default(ParseCtx &pctx) Invoke
parse_common()
with default options (no type flags)
-
template<typename ParseCtx, typename F, typename CharT = typename ParseCtx::char_type>
-
struct common_parser_default : public scn::common_parser
Derives from
common_parser
, and implementsparse()
withparse_default()
-
template<typename WrappedRange, typename Format, typename ...Args>
error scn::scan_usertype(basic_context<WrappedRange> &ctx, const Format &f, Args&... a) A convenience function for creating scanners for user-provided types.
Wraps vscan_usertype
Example use:
// Type has two integers, and its textual representation is // "[val1, val2]" struct user_type { int val1; int val2; }; template <> struct scn::scanner<user_type> : public scn::empty_parser { template <typename Context> error scan(user_type& val, Context& ctx) { return scan_usertype(ctx, "[{}, {}]", val.val1, val.val2); } };
- Parameters:
ctx – Context given to the scanning function
f – Format string to parse
a – Member types (etc) to parse
-
template<typename WrappedRange, typename CharT = typename WrappedRange::char_type>
error scn::vscan_usertype(basic_context<WrappedRange> &ctx, basic_string_view<CharT> f, basic_args<CharT> &&args) See also
See also
Low-level range reading
The following functions abstract away the source range in easier to understand parsing operations.
-
template<typename WrappedRange>
expected<typename WrappedRange::char_type> scn::read_code_unit(WrappedRange &r, bool advance = true) Reads a single character (= code unit) from the range.
Dereferences the begin iterator, wrapping it in an
expected
if necessary.Encoding-agnostic, doesn’t care about code points, and may leave behind partial ones.
- Parameters:
r – Range to read from
advance – If
true
, and the read was successful, the range is advanced by a single character, as if by callingr.advance()
.
- Returns:
The next character in the range, obtained as if by dereferencing the begin iterator
*r.begin()
. Ifr.begin() == r.end()
, returns EOF. Ifr
is direct, returns*r.begin()
wrapped in anexpected
. Ifr
is not direct, returns*r.begin()
as-is, with any errors that may have been caused by the read.
-
template<typename CharT>
struct read_code_point_result Type returned by
read_code_point
- Template Parameters:
CharT – Character type of the range
-
template<typename WrappedRange, typename BufValueT>
expected<read_code_point_result<typename WrappedRange::char_type>> scn::read_code_point(WrappedRange &r, span<BufValueT> writebuf) Read a single Unicode code point from
r
as if by repeatedly callingread_code_unit()
.Advances the range past the read code point. On error, rolls back the range into the state it was before calling this function, as if by calling
putback_n()
.- Parameters:
r – Range to read from
writebuf – Buffer to use for reading into, if necessary.
BufValueT
can be any trivial type. Must be at least 4 bytes long. May be written over.
- Returns:
An instance of
read_code_point_result
, wrapped in anexpected
.chars
contains the code units read fromr
, which may point towritebuf
.cp
contains the code point parsed. Ifr.begin() == r.end()
, returns EOF. Ifread_code_unit()
orputback_n()
fails, returns any errors returned by it. If the code point was not encoded correctly, returnserror::invalid_encoding
.
-
template<typename WrappedRange, typename std::enable_if<WrappedRange::provides_buffer_access>::type* = nullptr>
expected<span<const typename detail::extract_char_type<typename WrappedRange::iterator>::type>> scn::read_zero_copy(WrappedRange &r, ranges::range_difference_t<WrappedRange> n) Reads up to
n
characters (= code units) fromr
, as if by repeatedly incrementingr.begin()
, and returns aspan
pointing intor
.Let
count
bemin(r.size(), n)
. Reads, and advancesr
bycount
characters.r.begin()
is in no point dereferenced. Ifr.size()
is not defined, the range is not contiguous, and an empty span is returned.- Returns:
A
span
pointing tor
, starting fromr.begin()
and with a size ofcount
. Ifr.begin() == r.end()
, returns EOF. If the range does not satisfycontiguous_range
, returns an emptyspan
.
-
template<typename WrappedRange, typename std::enable_if<WrappedRange::is_contiguous>::type* = nullptr>
expected<span<const typename detail::extract_char_type<typename WrappedRange::iterator>::type>> scn::read_all_zero_copy(WrappedRange &r) Reads every character from
r
, as if by repeatedly incrementingr.begin()
, and returns aspan
pointing intor
.If there’s no error,
r
is advanced to the end.r.begin()
is in no point dereferenced. Ifr.size()
is not defined, the range is not contiguous, and an empty span is returned.- Returns:
A
span
pointing tor
, starting atr.begin()
and ending atr.end()
. Ifr.begin() == r.end()
, returns EOF. If the range does not satisfycontiguous_range
, returns an emptyspan
.
-
template<typename WrappedRange, typename OutputIterator, typename std::enable_if<WrappedRange::provides_buffer_access>::type* = nullptr>
error scn::read_into(WrappedRange &r, OutputIterator &it, ranges::range_difference_t<WrappedRange> n) Reads up to
n
characters (= code units) fromr
, as if by repeatedly callingread_code_unit()
, and writing the characters intoit
.If reading fails at any point, the error is returned.
r
is advanced by as many characters that were successfully read.- Parameters:
r – Range to read
it – Iterator to write into, e.g.
std::back_insert_iterator
. Must satisfyoutput_iterator
, and be incrementable byn
times.n – Characters to read from
r
- Returns:
error::good
ifn
characters were read. Ifr.begin() == r.end()
at any point beforen
characters has been read, returns EOF. Any error returned byread_code_unit()
if one occurred.
-
template<typename WrappedRange, typename Predicate>
expected<span<const typename WrappedRange::char_type>> scn::read_until_space_zero_copy(WrappedRange &r, Predicate &&is_space, bool keep_final_space) Reads code points from
r
, until a space, as determined byis_space
, is found, and returns aspan
pointing tor
.If no error occurs
r
is advanced past the returned span. On error,r
is not advanced.- Parameters:
r – Range to read from
is_space – Predicate taking a span of code units encompassing a code point, and returning a
bool
, wheretrue
means that the character is a space. Additionally, it must have a member functionis_space.is_multibyte()
, returning abool
, wheretrue
means that a space character can encompass multiple code units.keep_final_space – If
true
, the space code point found is included in the returned span, and it is advanced past inr
. Iffalse
, it is not included, andr.begin()
will point to the space.
- Returns:
Span of code units, pointing to
r
, starting atr.begin()
, and ending at the space character, the precise location determined by thekeep_final_space
parameter. Ifr.begin() == r.end()
, returns EOF.r
reaching its end before a space character is found is not considered an error. Ifr
contains invalid encoding, returnserror::invalid_encoding
. If the range is not contiguous, returns an emptyspan
.
-
template<typename WrappedRange, typename OutputIterator, typename Predicate, typename std::enable_if<WrappedRange::is_contiguous>::type* = nullptr>
error scn::read_until_space(WrappedRange &r, OutputIterator &out, Predicate &&is_space, bool keep_final_space) Reads code points from
r
, until a space, as determined byis_space
, is found, and writes them intoout
, a single code unit at a time.If no error occurs,
r
is advanced past the last character written intoout
.On error,
r
is advanced an indeterminate amount, as if by callingr.advance(n)
, wheren
is a non-negative integer. It is, however, not advanced past any space characters.- Parameters:
r – Range to read from
out – Iterator to write read characters into. Must satisfy
output_iterator
.is_space – Predicate taking a span of code units encompassing a code point, and returning a
bool
, wheretrue
means that the character is a space. Additionally, it must have a member functionis_space.is_multibyte()
, returning abool
, wheretrue
means that a space character can encompass multiple code units.keep_final_space – If
true
, the space code point found is written intoout
, and it is advanced past inr
. Iffalse
, it is not included, andr.begin()
will point to the space.
- Returns:
error::good
on success. Ifr.begin() == r.end()
, returns EOF.r
reaching its end before a space character is found is not considered an error. Ifr
contains invalid encoding, returnserror::invalid_encoding
.
-
template<typename WrappedRange, typename OutputIterator, typename Sentinel, typename Predicate>
error scn::read_until_space_ranged(WrappedRange &r, OutputIterator &out, Sentinel end, Predicate &&is_space, bool keep_final_space) Otherwise equivalent to
read_until_space
, except will also stop reading ifout == end
.See also
-
template<typename WrappedRange, typename std::enable_if<WrappedRange::is_contiguous>::type* = nullptr>
error scn::putback_n(WrappedRange &r, ranges::range_difference_t<WrappedRange> n) Puts back
n
characters (= code units) intor
as if by repeatedly callingr.advance(-1)
.Encoding-agnostic, may leave behind partial code points.
- Parameters:
r – Range to roll back
n – Characters to put back, must be less than or equal to the number of characters already read from
r
.
- Returns:
If
r
is contiguous, will always returnerror::good
. Otherwise, may returnerror::unrecoverable_source_error
, if the putback fails.
-
template<typename Context, typename std::enable_if<!Context::range_type::is_contiguous>::type* = nullptr>
error scn::skip_range_whitespace(Context &ctx, bool localized) noexcept Reads code points from
ctx.range()
, as if by repeatedly callingread_code_point()
, until a non-space character is found, or EOF is reached.That non-space character is then put back into the range.
Whether a character is a space, is determined by
ctx.locale()
and thelocalized
parameter.- Parameters:
ctx – Context to get the range and locale from.
localized – If
true
,ctx.locale().get_custom()
is used. Otherwise,ctx.locale().get_static()
is used. In practice, means whether locale-specific whitespace characters are accepted, or just those given bystd::isspace
with the"C"
locale.
- Returns:
error::good
on success. Ifctx.range().begin() == ctx.range().end()
, returns EOF. Ifctx.range()
contains invalid encoding, returnserror::invalid_encoding
.
Tuple scanning
-
template<typename ...Args, typename Range, typename Format>
auto scn::scan_tuple(Range &&r, Format f) -> std::tuple<detail::scan_result_for_range<Range>, Args...> Alternative interface for scanning, returning values as a tuple, instead of taking them by reference.
It’s highly recommended to use this interface only with C++17 or later, as structured bindings make it way more ergonomic.
Compared to the regular scan interface, the performance of this interface is the same (generated code is virtually identical with optimizations enabled), but the compile time is slower.
Values scanned by this function still need to be default-constructible. To scan a non-default-constructible value, use
scn::optional
- Parameters:
r – Input range
f – Format string to use
- Returns:
Tuple, where the first element is the scan result, and the remaining elements are the scanned values.
Utility types
-
template<typename CharT>
class basic_string_view A view over a (sub)string.
Used even when std::string_view is available to avoid compatibility issues.
-
using scn::string_view = basic_string_view<char>
-
using scn::wstring_view = basic_string_view<wchar_t>
-
template<typename T>
class span A view over a contiguous range.
Stripped-down version of
std::span
.
-
template<typename T>
class optional A very lackluster optional implementation.
Useful when scanning non-default-constructible types, especially with <tuple_return.h>:
// implement scn::scanner for optional<mytype> optional<mytype> val; scn::scan(source, "{}", val); // with tuple_return: auto [result, val] = scn::scan_tuple<optional<mytype>>(source, "{}");