Peregrin
Peregrin

Reputation: 436

variadic template arguments with default values

I have a templated struct that has a method that (along with other parameters) accepts those templated parameters.

template<class... Types>
struct Observer
{
    void subscribe(const std::string& id, Types... args)
    {
        // store id somehow
        data = std::make_tuple(args...);
    }

    std::tuple<Types...> data;
};

I want to make all the templated arguments optional. So that it looks like this:

Observer<float, int> observer;
observer.subscribe("id1", 3.14, 4);
observer.subscribe("id2", 0.707); // this doesn't work!
observer.subscribe("id3");        // this doesn't work!

As far as I know there is not straight forward way? But maybe someone know a workaround or a trick.

Ideally, I'd like to provide my own defaults. Maybe like this:

enum class SomeEnum { Val1, Val2 };    

Observer<float, SomeEnum, 0.f, SomeEnum::Val1> observer;
observer.subscribe("id1", 3.14);

Here is LIVE EXAMPLE.

Upvotes: 2

Views: 346

Answers (2)

Barry
Barry

Reputation: 304182

Boost.Mp11 for the win:

template <typename... Ts>
void subscribe(const std::string& id, Ts const&... args)
{
    static_assert(sizeof...(Ts) <= sizeof...(Types));
    using Rest = mp_drop_c<std::tuple<Types...>, sizeof...(Ts)>;
    data = std::tuple_cat(std::make_tuple(args...), Rest());
}

The assumes that value initialization of the trailing arguments is fine. If it's not, you'll have to figure out what to do with Us.


Works nicer if you actually make the optional more explicit:

template<class... Types>
struct Observer
{
    using Data = std::tuple<std::optional<Types>...>;

    template <typename... Ts>
    void subscribe(const std::string& id, Ts const&... args)
    {
        static_assert(sizeof...(Ts) <= sizeof...(Types));
        using Rest = mp_drop_c<Data, sizeof...(Ts)>;
        data = std::tuple_cat(std::make_tuple(args...), Rest());
    }

    Data data;
};

Upvotes: 1

Acorn
Acorn

Reputation: 26194

In C++17 you can simply do something like:

template<class... Types>
struct Observer
{
    static constexpr std::tuple<Types...> defaults{42, 24, 99};

    template<class... Args>
    void subscribe(Args... args)
    {
        if constexpr (sizeof...(Types) > sizeof...(Args)) {
            subscribe(args..., std::get<sizeof...(Args)>(defaults));
        } else {
            // whatever you need with `args...`
        }
    }
};

Here I am simply picking them from Observer::defaults, but feel free to compute them however you want.

For C++14 and below, you will need to emulate the if constexpr. See e.g. Constexpr if alternative for alternatives.

Upvotes: 2

Related Questions