scnlib  0.2.0
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages

Why take just views? Why not every possible range?

First off, it's not possible to take every range; operator-- is required for error recovery, so at least bidirectional_range is needed.

views have clearer lifetime semantics, and make it more difficult to write less performant code.

std::string str = "verylongstring";
auto ret = scn::scan(str, ...);
// str would have to be reallocated and its contents moved

Why take arguments by reference?

Relevant GitHub issue

Another frequent complaint is how the library requires default-constructing your arguments, and then passing them by reference. A proposed alternative is returning the arguments as a tuple, and then unpacking them at call site.

This is covered pretty well by the above GitHub issue, but to summarize:

  • std::tuple has measurable overhead (~5% slowdown)
  • it still would require your arguments to be default-constructible

To elaborate on the second bullet point, consider this example:

auto [result, i, str] =
scn::scan_tuple<int, non_default_constructible_string>(
range, scn::default_tag);

Now, consider what would happen if an error occurs during scanning the integer. The function would need to return, but what to do with the string? It must be default-constructed (std::tuple doesn't allow unconstructed members).

Would it be more convenient, especially with C++17 structured bindings? One could argue that, and that's why an alternative API, returning a tuple, is available, in the header <scn/tuple_return.h>. The rationale of putting it in a separate header is to avoid pulling in the entirety of very heavy standard headers <tuple> and <functional>.

What's with all the vscan, basic_args and arg_store stuff?

This approach is borrowed (cough stolen cough) from fmtlib, for the same reason it's in there as well. Consider this peace of code:

int i;
std::string str;
scn::scan(range, scn::default_tag, i, str);
scn::scan(range, scn::default_tag, str, i);

If the arguments were not type-erased, almost all of the internals would have to be instantiated for every given combination of argument types.