最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c++ - How to generalize this function with a variadic template - Stack Overflow

programmeradmin2浏览0评论

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()) };
    }
};

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()) };
    }
};
Share Improve this question asked Mar 27 at 22:55 girobuzgirobuz 1757 bronze badges 1
  • I assume the T1, T2 etc are not guaranteed to be distinct types, correct? – Chronial Commented Mar 27 at 23:52
Add a comment  | 

2 Answers 2

Reset to default 3

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()) ...);
}

Here is a version using std::apply to deal with the types in the pack.

class Parser {
    Lexer& lexer;

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

    Parser(Lexer& lexer);

    template<typename ReturnType, typename... T>
    inline std::optional<ReturnType> ParseMany() {
        auto tup = std::make_tuple(Parse<T>()...);
        return std::apply([](auto&... t) -> std::optional<ReturnType> {
            if ((t.has_value() && ...)) {
                return ReturnType{std::forward<T>(t.value())...};
            }
            return {};
        }, tup);
    }
};
发布评论

评论列表(0)

  1. 暂无评论