c++templatesvariadic-templates

How to generalize this function with a variadic template


I am trying to write a parser. I have the parser class below, but I am struggling to write a generalized ParseMany function with templates. Currently, to parse, I use parser.Parse<TypeToParse>(), which returns an optional with {} for a parsing error or TypeToParse if successful. I was wondering if it was possible to generalize the ParseMany function for a variable number of types. I am using visual studio 2022, C++ 20, and my project compiles and runs without errors.

class Parser {
    Lexer& lexer;

public:
    template<typename ReturnType>
    std::optional<ReturnType> Parse();

    Parser(Lexer& lexer);

    template<typename ReturnType, typename T1>
    inline std::optional<ReturnType> ParseMany() {
        std::optional<T1> maybe_t1 = Parse<T1>();
        if (!maybe_t1.has_value()) return {};
        return ReturnType{ std::forward<T1>(maybe_t1.value()) };
    }

    template<typename ReturnType, typename T1, typename T2>
    inline std::optional<ReturnType> ParseMany() {
        std::optional<T1> maybe_t1 = Parse<T1>();
        if (!maybe_t1.has_value()) return {};
        std::optional<T2> maybe_t2 = Parse<T2>();
        if (!maybe_t2.has_value()) return {};
        return ReturnType{ std::forward<T1>(maybe_t1.value()), std::forward<T2>(maybe_t2.value()) };
    }

    template<typename ReturnType, typename T1, typename T2, typename T3>
    inline std::optional<ReturnType> ParseMany() {
        std::optional<T1> maybe_t1 = Parse<T1>();
        if (!maybe_t1.has_value()) return {};
        std::optional<T2> maybe_t2 = Parse<T2>();
        if (!maybe_t2.has_value()) return {};
        std::optional<T3> maybe_t3 = Parse<T3>();
        if (!maybe_t3.has_value()) return {};
        return ReturnType{ std::forward<T1>(maybe_t1.value()), std::forward<T2>(maybe_t2.value()), std::forward<T3>(maybe_t3.value()) };
    }
};

Solution

  • Here's a basic version that works with arbitrary types in Ts:

    template<typename ReturnType, typename... Ts>
    std::optional<ReturnType> ParseMany() {
        auto maybe_tuple = ParseManyHelper<Ts...>();
        if (!maybe_tuple.has_value()) return std::nullopt;
        return std::make_from_tuple<ReturnType>(std::move(maybe_tuple.value()));
    }
    
    template<typename T, typename... Rest>
    std::optional<std::tuple<T, Rest...>> ParseManyHelper() {
        std::optional<T> maybe_t = Parse<T>();
        if (!maybe_t.has_value()) return std::nullopt;
    
        if constexpr (sizeof...(Rest) == 0) {
            return std::tuple<T>(std::move(maybe_t.value()));
        } else {
            auto maybe_rest = ParseManyHelper<Rest...>();
            if (!maybe_rest.has_value()) return std::nullopt;
            return std::tuple_cat(
                std::tuple<T>(std::move(maybe_t.value())),
                std::move(maybe_rest.value()));
        }
    }
    

    You can alternatively avoid the recursion by using fold expressions and std::index_sequence:

    template<typename ReturnType, typename... Ts>
    std::optional<ReturnType> ParseMany3() {
        return ParseManyHelper<ReturnType, Ts...>(
            std::index_sequence_for<Ts...>());
    }
    
    template<typename ReturnType, typename... Ts, std::size_t... Is>
    std::optional<ReturnType> ParseManyHelper(std::index_sequence<Is...>) {
        std::tuple<std::optional<Ts>...> maybe_values;
        bool all_present = ([&] {
            std::get<Is>(maybe_values) =
                Parse<typename std::tuple_element_t<Is, std::tuple<Ts...>>>();
            return std::get<Is>(maybe_values).has_value();
        }() && ...);
        if (!all_present) return std::nullopt;
        return ReturnType(std::move(std::get<Is>(maybe_values).value()) ...);
    }