【问题标题】:Verifying and passing around std::variant<> contents using parameter packs使用参数包验证和传递 std::variant<> 内容
【发布时间】:2022-01-22 18:53:54
【问题描述】:

我正在编写一个 sn-p 代码,它将自动检查 std::variants 中包含的类型,并根据提取的类型从它的子类中调用一个函数。我想出了一个我想做的有点有限的版本:

#include <variant>
#include <optional>
#include <string>
#include <vector>
#include <type_traits>
#include <stdexcept>
#include <tuple>
#include <iostream>

using atom = std::variant<std::string, int, double>;

class callable {
    public:
        virtual atom eval(std::vector<atom> args) = 0;
};

template <typename T>
struct name {
    static const char * get() {
        return typeid(T).name();
    }
};

template<typename T>
struct is_optional : std::false_type {};

template<typename T>
struct is_optional<std::optional<T>> : std::true_type {};

template<int N, typename... Ts> using pack_n =
    typename std::tuple_element<N, std::tuple<Ts...>>::type;

template <int N, typename... T>
void check(std::vector<atom> v) {
    if constexpr(!is_optional<pack_n<N, T...>>::value)
        if(v.size() != N)
            throw std::runtime_error("Wrong number of arguments to " + std::string(name<pack_n<N, T...>>::get()) + ", got " + std::to_string(v.size()));
        else {
            if(!std::holds_alternative<pack_n<N, T...>>(v[N]))
                throw std::runtime_error(std::string("Wrong type in argument ") + std::to_string(N) + name<pack_n<N, T...>>::get() + std::string(", got ") + std::to_string(v[N].index()));
            if constexpr(N > 0)
                check<N-1, T...>(v);
        }
    else {
        if (!std::holds_alternative<typename pack_n<N, T...>::value_type>(v[N]))
            throw std::runtime_error(std::string("Wrong type in argument ") + std::to_string(N) + name<pack_n<N, T...>>::get() + std::string(", got ") + std::to_string(v[N].index()));
        if constexpr(N > 0)
            check<N-1, T...>(v);
    }
}

template <typename... V>
class closure : public callable {
    public:
        virtual atom apply(V...);

        atom eval(std::vector<atom> args) override {
            check<sizeof...(V) - 1, V...>(args);
            return eval_impl(args, std::index_sequence_for<V...>{});
        }
    private:
        template<typename T>
        T get(atom a) {
            if constexpr(!is_optional<T>::value)
                return std::get<T>(a);
            else
                return std::optional(std::get<T>(a));
        }

        template<std::size_t... I>
        atom eval_impl(std::vector<atom> &args, std::index_sequence<I...>) {
          return apply(get<pack_n<I, V...>>(args[I])...);
        } 
};

class add : public closure<
    int, double, double, std::optional<int>
> {
    atom apply(int z, double a, double b, std::optional<int> c) {
        return a + b + *c;
    }
};

int main(void) {
    add f{ };
    std::vector<atom> v;
    v.push_back((int) 3);
    v.push_back((double) 5);
    v.push_back((double) 5);
    v.push_back((int) 3);
    std::cout << "ok?" << std::endl;
    std::cout << std::get<double>(f.eval(v));
    std::cout << "ok?" << std::endl;
}

期望的行为是调用子类 (add::apply) 中的 apply 方法并使用从变体中解压缩的参数,或者如果类型不匹配,代码将引发异常。另外,std::optionalstd::variant 应该被支持,这样apply 函数可以接受可选参数或指定它接受多种参数。

我已经实现了基本骨架和大部分std::optional 部分,但我仍在努力将它们与std::variant 联系在一起。我不确定如何使用 C++ 模板元编程完成以下任务:

  • eval_impl 中,允许输入向量的长度与参数包不同,以正确允许std::optional 参数。
  • 由于我无法追踪的原因,代码当前无法编译。
  • 如何在参数中支持std::variants。
  • 是否可以将apply 函数的参数用于模板,这样我就不必多次键入相同的内容。
  • 是否有可能以更清洁的方式完成这件事

澄清一下,我希望apply 函数的std::variants 允许任何一种类型,例如以下函数:

atom apply(int z, double a, double b, std::optional<int> c, std::variant<int, double> d);

可以在最后一个参数为intdouble 的情况下调用。

【问题讨论】:

  • 你在执行std::visit吗?即使我把你的帖子看了三遍,我也不知道你想达到什么目的。对我来说,这听起来像是一个 XY 问题!
  • 我没有实现std::visit。我想编写一些代码来自动检查std::variants 内部的类型,并使用从中取出的值调用函数。我的编程语言需要它,其中atom 表示数据类型。当然,callable 会有很多实现,所以这样的操作可以使代码更简洁。如果不是这样,我必须每次自己在实现中提取和验证 std::variant 内容 - 在整个代码库中可能超过 300-400 次。
  • “我想编写一些代码,自动检查 std::variants 内部的类型,并使用从中取出的值调用函数。”那正是std::visit。也许您希望仅存储某些 var 类型的 std::visit 调用,但这可以在提供给 std::visit 的模板化方法中实现...仍然不知道您想要实现什么...抱歉

标签: c++ templates metaprogramming variant


【解决方案1】:

据我了解,std::visit 可能会对您有所帮助:

template <typename... V>
class closure : public callable {
public:
    virtual atom apply(V...) = 0;

    atom eval(const std::vector<atom>& v) override
    {
        if (v.size() != sizeof...(V))
            throw std::runtime_error("Wrong number of arguments, expected" + std::to_string(sizeof...(V)) + ", got " + std::to_string(v.size()));

        return eval_impl(v, std::index_sequence_for<V...>{});
    }
private:
    template<std::size_t... Is>
    atom eval_impl(const std::vector<atom>& v, std::index_sequence<Is...>) {
        auto visitor = overloaded {
           [this](auto... args) -> decltype(this->apply(args...)) { return this->apply(args...); },
           [](auto... args) -> std::enable_if_t<!std::is_invocable_v<decltype(&closure::apply), closure*, decltype(args)...>, atom> { throw "bad argument"; }
        };
        return std::visit(visitor , v[Is]...);
    } 
};

Demo

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-08
    • 2022-08-18
    • 2021-08-24
    • 2023-01-30
    • 1970-01-01
    • 2020-06-05
    相关资源
    最近更新 更多