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;
}
\$\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");
}
\$\endgroup\$
1
  • \$\begingroup\$ Thank you for your review, will take your input into considerations \$\endgroup\$
    – G. Nass
    Mar 21 at 21:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.