1

I'd like to write a C++11 class that mimics the behavior of a mathematical function. The class takes as an input the sets upon which the function is defined, and it is possible to set and get the value associated with a specific point in the domain.

Since the number of sets that comprise the function domain is not know a priori, I'd like to use C++11 variadic templates to define the class as follows:

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

So that a new function can be created as follows:

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

where S1, S2 and S3 are std::set<int>, std::set<std::string> and std::set<double>, respectively. Setting and getting a value should resemble what happens with std::tuple:

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

Most probably, std::unordered_map is the ideal data member to store the binding between domain and codomain:

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

I tried to adapt the code from Initialzing and accessing members of a variadic class template, even though the two problems are not identical (in my case I may not need to store each single std::set).

EDIT #1: I'll try to better stress the issue I'm facing. In the linked question, the class is created by means of recursive calls. However, in my case I'm having troubles in understanding how to implement the constructor, i.e., how to set the domain of the function starting from the input sets. One possible way would be to use the constructor to pre-fill all the keys generated by the Cartesian product of the input sets for the data_ data member. The problem is that I don't know how to iterate over the parameter pack.

Tentative solution #1

Here's a tentative implementation: pastebin.com/FMRzc4DZ based on the contribution of @robert-mason. Unfortunately, it does not compile (clang 4.1, OSX 10.8.4) as soon as is_in_domain() is called. However, at first sight everything seems fine. What could be wrong?

Community
  • 1
  • 1
Ilio Catallo
  • 3,152
  • 2
  • 22
  • 40
  • What is the question? What have you tried? – mfontanini Jul 19 '13 at 13:44
  • I tried to implement the class exactly as described in the question. Indeed, I did not provide the code, but I'm afraid that it would not be of much help. That's why I prefered to describe my problem. – Ilio Catallo Jul 19 '13 at 13:57
  • So for an N-dimensional `Function`, is the N-tuple that you `set()` or `get()` mapped to the value, or is each element mapped to a value? It seems to me like you would want to just set *one* key to a value. – Robert Mason Jul 19 '13 at 14:31
  • That is to say, do you have a tuple of maps or a map of tuples? – Robert Mason Jul 19 '13 at 14:32
  • I have a map of tuples. So that to each point in the function domain, i.e., a tuple `` (the map key), corresponds one and only one point in the codomain, i.e., the `double` value associated to the tuple. – Ilio Catallo Jul 19 '13 at 14:34
  • @mfontanini: The question is "any help?" Duh! – Lightness Races in Orbit Jul 19 '13 at 14:58

1 Answers1

2

I'm leaving my original answer below, but I'll try to address your question with variadic templates.

With a variadic function template, you do not iterate over the parameter pack. You must instead use a recursive function.

What I would use then would be something like:

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

The trick is the combination of recursion and SFINAE. We need std::enable_if<> to prevent the compiler from expanding the calls to std::get<>(), as the index checking for get is done statically and will cause a compile error even if it will never be executed.

Possible areas of improvement would be making construction more efficient by moving the sets if you can. This would require perfect forwarding and other template magic, since you'd have to let template argument deduction deduce the types so that reference collapsing kicks in and then use a static_assert() to error when the deduced type is not the expected type (i.e. !(std::is_same<T, std::remove_cv<std::remove_reference<FirstDomain>::type>::type>::value), but in variadic form) and then forwarding to the set with std::forward().


(original answer)

In this case, you don't want to use the parameters as template arguments. There are all sorts of rules concerning template arguments that you don't want to have to deal with - specifically that all of the arguments have to be integral constant expressions.

You just want to use "normal" arguments, so that you can easily pass them in to the std::unordered_map, can be of any type, and can be runtime-defined:

I would recommend something like:

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

You can do some syntactic sugar if you like to make this look nicer:

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

Which lets you then do:

Function<int, std::string, double> three_dim_function;
three_dim_function.set(1, "a", 1.23) = 12;
Robert Mason
  • 3,949
  • 3
  • 31
  • 44
  • Thanks for your answer. What about the issue of dealing with an unknown number of input sets? I do not want to create a class per dimensionality of the math function (e.g., 2DFunction, 3DFunction). – Ilio Catallo Jul 19 '13 at 14:00
  • I don't understand your question. If you look at the short code sample that will handle all of them. You can use the enclosing class's variadic parameter pack as a argument type to the function. – Robert Mason Jul 19 '13 at 14:10
  • I updated the question, I hope that the issue is clearer now. Thanks. – Ilio Catallo Jul 19 '13 at 14:14
  • The input sets are finite. The `Function` class associates each tuple in the form `` (with `n` representing the number of input sets) with a double scalar. Since the function is defined only for those points obtained as the Cartesian product of the input sets, you must know in advance whether a specific point provided to the setter/getter is indeed a valid point or not. One possible way to check this is to create in the constructor all the possible keys for `data_`. In the setter and getter you may use `data_.at(point)` to verify if the point belongs to the function domain. – Ilio Catallo Jul 19 '13 at 14:29
  • OK thanks that's clearer. Give me a sec and I'll hammer something out. – Robert Mason Jul 19 '13 at 14:36
  • Updated with clarified data. Since computing and storing the Cartesian product of the sets seems like major overkill to me, I just stored each dimension and checked each by something resembling iteration. – Robert Mason Jul 19 '13 at 14:57
  • An additional issue is how to implement `std::hash>`, I'm trying to recursivly call `std::hash`, but the problem is how to create a new sub-`std::tuple`, which is equal to the original up to the first element. – Ilio Catallo Jul 19 '13 at 15:30
  • See http://stackoverflow.com/questions/15103975/my-stdhash-for-stdtuples-any-improvements, but read the comments. If XOR is unsatisfactory to combine hashes you can look at Boost's hash_combine http://www.boost.org/doc/libs/1_33_1/doc/html/hash_combine.html – Robert Mason Jul 19 '13 at 15:41
  • Thank you. I'll report a working implementation for Function ASAP. – Ilio Catallo Jul 19 '13 at 16:02
  • Here's a tentative implementation: http://pastebin.com/FMRzc4DZ. Unfortunately, it does not compile (clang 4.1, OSX 10.8.4) as soon as `is_in_domain()` is called. However, at first sight everything seems fine. – Ilio Catallo Jul 19 '13 at 16:41
  • I updated my answer so that your example will compile. I did some very basic testing to verify that it gave the correct results but didn't do anything rigorous. I would still test it on higher dimension functions. – Robert Mason Jul 19 '13 at 20:31
  • It works. I tried to write a `double& operator()(set_type first_set, addl_set_types... addl_sets)` member function, but unfortunately I'm suffering from this bug: http://llvm.org/bugs/show_bug.cgi?id=16542. I'll try to post the complete code as soon as I check the behavior in gcc. – Ilio Catallo Jul 24 '13 at 10:49