I am trying to parse a line with a fixed amount of fields and known types.
using Text = FieldType<std::string>;
using Int = FieldType<int>;
using Name = Text;
using TextSpecificField = Text;
using IntSpecificField = Int;
struct LineData
{
Name name;
TextSpecificField textField;
IntSpecificField intField;
// LineData contains 12 fields in total, types are known
};
struct InvalidLine { std::string line, err; };
using Line = std::variant<LineData, InvalidLine>;
With that, I can write a parsing function for each specific field type:
template<Field T>
constexpr auto parse(std::string_view field) -> detail::ReturnOptErr<T>
{
if constexpr (std::is_same_v<Text, T>)
{
return {decltype(T::value){field}};
}
else if constexpr (std::is_same_v<Int, T>)
{
auto [value, err] = toNum<decltype(T::value)>(field); // Why typename T::value doesn't work?
return {T{value}, std::move(err)};
}
else
{
[]<bool flag = false> { static_assert(flag, "no match"); }();
}
}
Here my problem starts with the below parse function. I am having trouble wrapping my head around trying to simplify this piece of code. As it can be seen, there is a repeating pattern that I would like to abstract:
auto parse(std::string_view line) -> Line
{
// Parsing a string and returning string is done intentionally to show repeating pattern
auto [nameStr, rest] = split(line, ',');
auto name = parse<Name>(nameStr);
if (name.errorText)
{
return InvalidLine{std::string{line}, std::move(name.errorText.value())};
}
auto [textStr, rest2] = split(rest, ',');
auto textField = parse<TextSpecificField>(textStr);
if (textField.errorText)
{
return InvalidLine{std::string{line}, std::move(textField.errorText.value())};
}
auto [intStr, rest3] = split(rest2, ',');
auto intField = parse<IntSpecificField>(intStr);
if (intField.errorText)
{
return InvalidLine{std::string{line}, std::move(intField.errorText.value())};
}
return LineData{std::move(name.value), std::move(textField.value), intField.value};
}
Is there an elegant way to approach this? I thought about using exceptions, but I don't like that solution. I also tried to approach this problem using template recursion, but I could not come up with an idea of returning Line
from such a parser.
BR
#include <algorithm>
#include <array>
#include <functional>
#include <iostream>
#include <optional>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <variant>
// - Utility ---------------------------------------------------------------------------------------
namespace detail
{
template <typename FieldType>
struct ReturnOptErr
{
FieldType value;
std::optional<std::string> errorText;
};
} // namespace detail
template<typename T>
constexpr auto toNum(std::string_view valueStr) -> detail::ReturnOptErr<T>
{
// Implementation of number parsing using <charconv>
return detail::ReturnOptErr<T>{123};
}
constexpr auto split(std::string_view input, char delim) noexcept -> std::pair<std::string_view, std::string_view>
{
auto const delimPos = input.find(delim);
if (delimPos == std::string_view::npos)
{
return {input, ""};
}
auto const token = input.substr(0, delimPos);
auto const rest = input.substr(delimPos + 1);
return {token, rest};
}
// - Data ------------------------------------------------------------------------------------------
template<typename T>
struct FieldType
{
T value;
};
template<typename T>
concept Field = std::is_same_v<FieldType<decltype(T::value)>, T>;
auto operator<<(std::ostream& os, Field auto const& field) -> std::ostream&
{
return os << field.value;
}
using Text = FieldType<std::string>;
using Int = FieldType<int>;
using Name = Text;
using TextSpecificField = Text;
using IntSpecificField = Int;
// more field types
// ------------------------------------------------------------------------------------------------
struct LineData
{
Name name;
TextSpecificField textField;
IntSpecificField intField;
// LineData containts 12 fields in total, types are known
};
struct InvalidLine { std::string line, err; };
using Line = std::variant<LineData, InvalidLine>;
// - Parser ----------------------------------------------------------------------------------------
template<Field T>
constexpr auto parse(std::string_view field) -> detail::ReturnOptErr<T>
{
if constexpr (std::is_same_v<Text, T>)
{
return {decltype(T::value){field}};
}
else if constexpr (std::is_same_v<Int, T>)
{
auto [value, err] = toNum<decltype(T::value)>(field); // Why typename T::value doesn't work?
return {T{value}, std::move(err)};
}
else
{
[]<bool flag = false> { static_assert(flag, "no match"); }();
}
}
auto parse(std::string_view line) -> Line
{
// Parsing a string and returning string is done intentionally to show repeating pattern
auto [nameStr, rest] = split(line, ',');
auto name = parse<Name>(nameStr);
if (name.errorText)
{
return InvalidLine{std::string{line}, std::move(name.errorText.value())};
}
auto [textStr, rest2] = split(rest, ',');
auto textField = parse<TextSpecificField>(textStr);
if (textField.errorText)
{
return InvalidLine{std::string{line}, std::move(textField.errorText.value())};
}
auto [intStr, rest3] = split(rest2, ',');
auto intField = parse<IntSpecificField>(intStr);
if (intField.errorText)
{
return InvalidLine{std::string{line}, std::move(intField.errorText.value())};
}
return LineData{std::move(name.value), std::move(textField.value), intField.value};
}
int main()
{
std::string lineStr = "name, text, 123456";
auto lineVar = parse(lineStr);
auto& line = std::get<LineData>(lineVar);
std::cout << line.name << ", " << line.textField << ", " << line.intField << "\n";
}