Adelhart
Adelhart

Reputation: 425

How do I unwrap a parameter pack of length 1 (containing a single value)?

I am writing a little variadic summing function (using c++20, but my question would remain the same with c++17 syntax). I would like to make the following code as short and clear as possible (but without using folding expressions. This is only a toy problem, but in later applications I would like to avoid fold expressions):

Additive auto sum(Additive auto&& val, Additive auto&&... vals) {
  auto add = [](Additive auto&& val1, Additive auto&& val2) {
      return val1 + val2;
  }; // neccessary??
  if constexpr(sizeof...(vals) == 1) {
      return add(val, std::forward<decltype(vals)>(vals)...); // (1)
      //return val + std::forward<decltype(vals)>(vals)...; // (2)
    }
  else return val + sum(std::forward<decltype(vals)>(vals)...);  
}

Using line (1) the above code compiles, but it makes the definition of the 'add' lambda neccessary. Line (2), however, does not compile, I get the following error with gcc: parameter packs not expanded with ‘...’. If I add parentheses around the std::forward expression in line (2), I get the following error: expected binary operator before ‘)’ token.

Is there any way to pass a parameter pack with length 1 to an operator?

Upvotes: 1

Views: 323

Answers (3)

dumbass
dumbass

Reputation: 27208

Embrace the power of negative thinking and start induction with zero instead of one:

auto sum(auto &&val, auto &&...vals) {
    if constexpr (sizeof...(vals) == 0)
        return val;
    else
        return val + sum(std::forward<decltype(vals)>(vals)...);  
}

The above definition has the side effect that sum(x) will now compile and return x. (In fact, you can even make the function work with no arguments, by having it return zero, but then the question arises: zero of which type? To avoid having to go there, I left this case undefined.) If you insist on sum being defined only from arity 2 upwards, you can use this instead:

auto sum(auto &&val0, auto &&val1, auto &&...vals) {
    if constexpr (sizeof...(vals) == 0)
        return val0 + val1;
    else
        return val0 + sum(std::forward<decltype(val1)>(val1),
            std::forward<decltype(vals)>(vals)...);
}

However, you should probably allow the ‘vacuous’ case whenever it makes sense to do so: it makes for simpler and more general code. Notice for example how in the latter definition the addition operator appears twice: this is effectively duplicating the folding logic between the two cases (in this case it’s just one addition, so it’s relatively simple, but with more complicated operations it might be more burdensome), whereas handling the degenerate case is usually trivial and doesn’t duplicate anything.

(I omitted concept annotations, as they do not seem particularly relevant to the main problem.)

Upvotes: 4

Artyer
Artyer

Reputation: 40836

You can unpack the one item into a variable and use that:

if constexpr (sizeof...(vals) == 1) {
    auto&& only_value(std::forward<decltype(vals)>(vals)...);
    return val + only_value;
}

Upvotes: 2

bipll
bipll

Reputation: 11940

template<class... Additive> decltype(auto) sum(Additive &&...val) {
    return (std::forward<Additive>(val) + ...);
}

?

Offtopic: unsure about Op's real needs, I've accidentally quickdesigned one thing I've been thinking of, from time to time. :D

#include <iostream>
#include <functional>
#include <type_traits>

template<class... Fs> struct Overloads;

template<class F, class... Fs> struct Overloads<F, Fs...>: Overloads<Fs...> {
        using Fallback = Overloads<Fs...>;

        constexpr Overloads(F &&f, Fs &&...fs): Fallback(std::forward<Fs>(fs)...), f(std::forward<F>(f)) {}

        template<class... Args> constexpr decltype(auto) operator()(Args &&...args) const {
                if constexpr(std::is_invocable_v<F, Args...>) return std::invoke(f, std::forward<Args>(args)...);
                else return Fallback::operator()(std::forward<Args>(args)...);
        }
private:
        F f;
};

template<class... Fs> Overloads(Fs &&...fs) -> Overloads<Fs...>;

template<class F> struct Overloads<F> {
        constexpr Overloads(F &&f): f(std::forward<F>(f)) {}

        template<class... Args> constexpr decltype(auto) operator()(Args &&...args) const {
                return std::invoke(f, std::forward<Args>(args)...);
        }
private:
        F f;
};

template<> struct Overloads<> {
        template<class... Args> constexpr void operator()(Args &&...) const noexcept {}
};

constexpr int f(int x, int y) noexcept { return x + y; }

void g(int x) { std::cout << x << '\n'; }

template<class... Vals> decltype(auto) omg(Vals &&...vals) {
        static constexpr auto fg = Overloads(f, g);
        return fg(std::forward<Vals>(vals)...);
}

int main() {
        omg(omg(40, 2));
}

>_<

Upvotes: 3

Related Questions