5
\$\begingroup\$

simplifying this question, taking most of the comment into considerations, I ended up with this version, looking for some feedback

#include <iostream>
#include <tuple>
#include <cassert>
#include <type_traits>
std::tuple<std::string_view, std::string_view> split(std::string_view str, char delimiter)
{
 auto pos = str.find(delimiter);
 if (pos == std::string_view::npos)
 {
 return {str, {}};
 }
 return {str.substr(0, pos), str.substr(pos + 1)};
}
template <typename T>
auto parse(std::string_view str)
{
 using T2 = std::unwrap_ref_decay_t<T>;
 T2 res;
 if constexpr (std::is_same_v<T2, std::string>) { return res = std::string(str);}
 if (std::is_floating_point_v<T2>){ return res = std::stod(std::string(str));return res;}
 if (std::is_integral_v<T2>) { return res = std::stol(std::string(str));}
 throw std::invalid_argument("Invalid type");
}
template <typename T>
constexpr bool is_tuple = false;
template <typename T>
constexpr bool has_content = false;
template <typename T>
constexpr bool is_constructible = false;
template <typename... Ts>
constexpr bool is_tuple<std::tuple<Ts...>> = true;
template <typename... Ts>
constexpr bool has_content<std::tuple<Ts...>> = sizeof...(Ts) > 0;
template <typename... Ts>
constexpr bool is_constructible <std::tuple<Ts...>> = std::conjunction_v<std::is_default_constructible<Ts>...>;
template <typename TupleType>
requires is_tuple<TupleType> && 
 has_content<TupleType> &&
 is_constructible<TupleType>
auto handle(std::string_view str)
{
 TupleType result;
 auto parse_one = [&](auto &arg)
 {
 auto [ field, rest ] = split(str, ',');
 arg = parse<decltype(arg)>(field);
 str = rest;
 };
 std::apply([&](auto &...args)
 { (parse_one(args), ...); },
 result);
 return result;
}
int main()
{
 using LineData = std::tuple<std::string, double, int64_t>;
 std::string_view input_str = "first_cut,55.3,177";
 LineData result = handle<LineData>(input_str);
 std::cout << "Parsed values:" << '\n';
 std::cout << "String value: " << std::get<0>(result) << '\n';
 std::cout << "Double value: " << std::get<1>(result) << '\n';
 std::cout << "Int value: " << std::get<2>(result) << '\n';
 return 0;
}
Sᴀᴍ Onᴇᴌᴀ
29.5k16 gold badges45 silver badges201 bronze badges
asked Mar 20, 2023 at 16:15
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

Simplify the constraints

You defined three type trait checkers: is_tuple, has_content and is_constructible. Note that has_content is also only ever true if the type passed is a std::tuple, so is_tuple is redundant.

The names are also wrong: has_content doesn't say anything about it checking for tuples, and one would say that a non-empty std::vector also "has content". Maybe is_nonempty_tuple is better? One could also argue that while an empty tuple is a corner case, the code you've written will actually handle that correctly, so there is no need to prevent it.

is_constructible is also misnamed: again despite the name not saying it, it only is ever true for std::tuples. But also note that "constructible" is not the same as "default constructible". So it should have been named is_default_constructible_tuple. However, there is no need to make such a check; std::is_default_constructible_v<TupleType> already checks that the whole tuple can be default constructed.

While you check that TupleType is a default-constructible, non-empty tuple, you didn't check whether the types stored in the tuple can actually be parsed. You could write a concept that checks whether you provide a default-constructible, non-empty, parsable std::tuple, but it would just duplicate the whole body of handle() inside a requires expression, which defeats the point.

I'd rather keep it simple:

template <typename TupleType>
requires is_tuple<TupleType> && 
 std::is_default_constructible_v<TupleType>
auto handle(std::string_view str) ...

Avoid needing the tuple type to be default-constructible

You can avoid default-constructing a TupleType by making use of the fact that you can return a value from std::apply(), and that you can already refer to a variable's name when constructing it:

auto handle(std::string_view str)
{
 auto parse_one = [&](auto &arg)
 {
 auto [ field, rest ] = split(str, ',');
 str = rest;
 return parse<decltype(arg)>(field);
 };
 TupleType result = std::apply([&](auto &...args)
 { return TupleType{parse_one(args)...}; },
 result);
 return result;
}

Simplify returning values from parse()

I think your parse() is unnecessarily complex because you forgot to add constexpr to the second and third if-statement. I think you can completely avoid the temporary variable res:

template <typename T>
auto parse(std::string_view str)
{
 using T2 = std::unwrap_ref_decay_t<T>;
 if constexpr (std::is_same_v<T2, std::string>) { return str; }
 if constexpr (std::is_floating_point_v<T2>) { return std::stod(std::string(str)); }
 if constexpr (std::is_integral_v<T2>) { return std::stol(std::string(str)); }
 throw std::invalid_argument("Invalid type");
}

Prefer using std::from_chars() to parse numeric values

Unfortunately, std::stod() and std::stoi() don't take std::string_views as arguments, as you have discovered. I recommend you use std::from_chars() instead. It also doesn't take std::string_views, but it takes pointers to the start and end of the string, which you can easily create from a std::string_view. It also avoids having to deal with integers and floating point values separately:

if constexpr (std::is_arithmetic_v<T2>) {
 if (T2 value; std::from_chars(str.begin(), str.end(), value) == std::errc{})
 return value;
 else
 throw std::runtime_error("Parse error");
}
answered Mar 21, 2023 at 21:40
\$\endgroup\$
1
  • \$\begingroup\$ Thank you for your review, will take your input into considerations \$\endgroup\$ Commented Mar 21, 2023 at 21:47

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.