2

Is it possible to achieve the following using a fold expression?

template<class... Args>
auto foo(Args... args)
{
    //calling foo(x0, x1, x2) should be exactly equivalent to
    //calling fn(x2 ^ fn(x1 ^ fn(x0)))
}
Chris_F
  • 4,991
  • 5
  • 33
  • 63
  • 2
    Is there a reason that it has to be a fold expression? This seems like a situation where a simple recursive call with an overload on the number of function arguments would be a much more straight-forward approach. – user17732522 Oct 16 '22 at 02:51
  • @user17732522 Are you saying the answer is no? – Chris_F Oct 16 '22 at 03:07
  • Much simpler would be to put the arguments in an `initializer_list` and iterate or `std::accumulate` over it, assuming the args are all the same type. – Raymond Chen Oct 16 '22 at 16:07
  • @RaymondChen Initializer list would only work if all arguments are of the same type, wouldn't it? As declared, `foo` may accept arguments of different types. – Igor Tandetnik Oct 16 '22 at 16:17
  • Right, I was assuming they were all the same type. Presumably integral, given the `^` operator. Even if different type with overloaded `^`, you can do `int v = 0; auto acc = [&](auto&& x) { v = fn(x^v);}; (acc(args), ...); return v;` - this assumes that `x ^ 0 = x`. – Raymond Chen Oct 16 '22 at 16:55
  • @RaymondChen Conceivably, `^` could be overloaded and `fn` could be overloaded, and none of the types involved could be convertible to `int`. – Igor Tandetnik Oct 16 '22 at 18:14

2 Answers2

5

You could add a mandatory argument to foo and check if you've got additional arguments with a constexpr-if inside.

Edit: I accidentally missed the requirement that it should be a fold expression so this uses pack expansion and recursion.

template <class T, class... Args>
constexpr auto foo(T&& x, Args&&... args) {    
    if constexpr (sizeof...(args)) {
        return fn(x ^ foo(std::forward<Args>(args)...));
    } else {
        return fn(x);
    }
}

This requires that you reverse the arguments when calling the function though:

  • foo(x2, x1, x0) => fn(x2 ^ fn(x1 ^ fn(x0)))

Demo

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • This doesn't use a fold expression though, as was the original requirement. It's just a recursion, as @user17732522 suggested in the very first comment. If the requirement of using fold expression is dropped, your approach could be [tweaked slightly](https://godbolt.org/z/EM4rKeeee) to preserve the original order of arguments. – Igor Tandetnik Oct 17 '22 at 14:13
  • @IgorTandetnik Oh, yes, you are correct. I was too focused on making it work so I silently dropped the folding part. My bad. Added a note to the answer about it. – Ted Lyngmo Oct 17 '22 at 14:58
3

If you insist on a fold expression, something along these lines could probably be made to work (not tested):

template <typename T>
struct Wrapper {
  T& val;
};

template <typename T, typename U>
auto operator^(Wrapper<T> l, Wrapper<U> r) {
  return Wrapper(r.val ^ fn(l.val));
}

template<class... Args>
auto foo(Args... args)
{
  return fn((... ^ Wrapper<Args>(args)).val);
}
Igor Tandetnik
  • 50,461
  • 4
  • 56
  • 85
  • This doesn't work: https://godbolt.org/z/c7Khzznfa – MyClass Oct 17 '22 at 07:13
  • @MyClass Well, I didn't promise it worked, only that it could [be made to work](https://godbolt.org/z/8f9qo4oqW) (note that your original `static_assert` has the arguments in the wrong order; the OP wanted the leftmost argument to be the deepest-nested). – Igor Tandetnik Oct 17 '22 at 13:01
  • I'm accepting your answer because you gave me what I asked for. Ted's answer is probably the better solution if you are willing to accept that the order of the arguments are reversed. – Chris_F Oct 17 '22 at 13:49
  • @Chris_F That solution doesn't use a fold expression though, as you for some reason insisted on. The order of arguments issue is [easy enough to fix](https://godbolt.org/z/EM4rKeeee) in that approach, but the lack of fold expression isn't. – Igor Tandetnik Oct 17 '22 at 14:16