1

我想编写一个模仿数学函数行为的 C++11 类。该类将定义函数的集合作为输入,并且可以设置和获取与域中特定点关联的值。

由于构成函数域的集合数量是先验的,因此我想使用 C++11 可变参数模板来定义类,如下所示:

template<typename first_set_type, typename... additional_sets_type> class Function;

这样就可以创建一个新函数,如下所示:

Function<int, std::string, double> three_dim_function(S1, S2, S3);

其中S1,S2S3std::set<int>,std::set<std::string>std::set<double>, 分别。设置和获取值应该类似于发生的情况std::tuple

three_dim_function.set<1, "a", 1.23>(12);
double twelve = three_dim_function.get<1, "a", 1.23>();

最有可能std::unordered_map是存储域和 codomain 之间绑定的理想数据成员:

std::unordered_map<std::tuple<first_set_type, additional_set_types...>, double> data_;

我尝试从Initialzing 和访问可变参数类模板的成员中调整代码,即使这两个问题并不相同(在我的情况下,我可能不需要存储每个单独的问题std::set)。

编辑#1:我会尝试更好地强调我面临的问题。在链接的问题中,类是通过递归调用创建的。但是,就我而言,我在理解如何实现构造函数方面遇到了麻烦,即如何从输入集开始设置函数的域。一种可能的方法是使用构造函数来预填充由data_数据成员的输入集的笛卡尔积生成的所有键。问题是我不知道如何迭代参数包。

暂定解决方案#1

这是一个暂定的实现:pastebin.com/FMRzc4DZ基于@robert-mason 的贡献。不幸的是,它不会is_in_domain()在调用时立即编译(clang 4.1,OSX 10.8.4)。然而,乍一看,一切似乎都很好。有什么问题?

4

1 回答 1

2

我将在下面留下我的原始答案,但我会尝试使用可变参数模板来解决您的问题。

使用可变参数函数模板,您无需遍历参数包。您必须改为使用递归函数。

我会使用的东西是这样的:

template <class FirstDomain, class ...Domains>
class Function {
public:
    typedef std::tuple<FirstDomain, Domains...> domain_t;
    static constexpr size_t dimension = sizeof...(Domains) + 1; //+1 for FirstDomain
private:
    std::tuple<std::set<FirstDomain>, std::set<Domains>...> domain;
    std::unordered_map<domain_t, double> map;

    template <size_t index = 0>
    typename std::enable_if<(index < dimension), bool>::type
    is_in_domain(const domain_t& t) const {
        const auto& set = std::get<index>(domain);
        if (set.find(std::get<index>(t)) != set.end()) {
            return is_in_domain<index + 1>(t);
        }
        return false;
    }
    template <size_t index = 0>
    typename std::enable_if<!(index < dimension), bool>::type
    is_in_domain(const domain_t& t) const {
        return true;
    }
public:
    Function(std::set<FirstDomain> f, std::set<Domains>... ds) :
        : domain(f, ds...) {}
};

诀窍是递归和 SFINAE 的结合。我们需要std::enable_if<>防止编译器扩展对 的调用std::get<>(),因为对 get 的索引检查是静态完成的,即使它永远不会执行也会导致编译错误。

如果可以的话,可能的改进领域是通过移动设备来提高施工效率。这将需要完美的转发和其他模板魔术,因为您必须让模板参数推导推导类型,以便引用折叠启动,然后static_assert()在推导的类型不是预期类型时使用 to 错误(即!(std::is_same<T, std::remove_cv<std::remove_reference<FirstDomain>::type>::type>::value),但在可变参数中)形式),然后转发到集合std::forward()


(原答案)

在这种情况下,您不想将参数用作模板参数。关于模板参数的各种规则是您不想处理的——特别是所有参数都必须是整数常量表达式。

您只想使用“普通”参数,以便您可以轻松地将它们传递给std::unordered_map,可以是任何类型,并且可以是运行时定义的:

我会推荐类似的东西:

three_dim_function.set(1, "a", 1.23, 12);
double twelve = three_dim_function.get(1, "a", 1.23);

如果你想让它看起来更好,你可以做一些语法糖:

template <class first_type, class ...addl_types>
class Function {
public:
    //...
    //left as an exercise for the reader
    void set(std::tuple<first_type, addl_types...>, double);

    class set_proxy {
        friend class Function<first_type, addl_types...>;
        std::tuple<first_type, addl_types...> input;
        Function<first_type, addl_types...>& parent;
        set_proxy(std::tuple<first_type, addl_types...> t, Function<first_type, addl_types...>& f)
            : input(t), parent(f) {}
        set_proxy(const set_proxy&) = delete;
        set_proxy& operator=(const set_proxy&) = delete;
    public:
        //yes, I know this isn't the right return type, but I'm not sure what's idiomatic
        void operator=(double d) {
            parent.set(input, d);
        }
    };

    set_proxy set(first_type f, addl_types... addl) {
        return set_proxy{std::make_tuple(f, addl...), *this};
    }
};

然后,您可以这样做:

Function<int, std::string, double> three_dim_function;
three_dim_function.set(1, "a", 1.23) = 12;
于 2013-07-19T13:49:02.373 回答