7

Here is a nice, succinct fold expression based lambda in C++17:

#include <cstdint>

using ::std::uint64_t;

constexpr auto sumsquares = [](auto... n) { return ((n * n) + ...); };

// I want this to work.
uint64_t foo(uint64_t x, uint64_t y, uint64_t z)
{
    return sumsquares(x, y, z);
}
// And this too
double bar(uint64_t x, double y)
{
    return sumsquares(x, y);
}

I have this code I've written to do something similar in C++14, but it seems a lot more verbose and confusing than it should be. I'm looking for a way to express the above C++17 code in C++14 in a way that's relatively clear and succinct. To be precise, I want to be able to write code that computes the square of the magnitude of a vector for a vector of some known number of dimensions with a function-call like syntax. The number of dimensions may vary arbitrarily though. And the precise numeric type of the individual components of the coordinate system may also be arbitrary and possibly heterogeneous. But a general way to handle C++17 fold expressions in C++14 would be ideal.

#include <cstdint>
#include <utility> 

using ::std::uint64_t;

namespace {
    static constexpr struct {
        template <typename T>
        auto operator()(T && n) const
        {
           return n*n;
        }
        template <typename T, typename... S>
        auto operator()(T && n, S && ... s) const
        {
            return (n * n) + (*this)(::std::forward<S>(s)...);
        }
    } sumsquares;
}

// I want this to work.
uint64_t foo(uint64_t x, uint64_t y, uint64_t z)
{
    return sumsquares(x, y, z);
}
// And this too
double bar(uint64_t x, double y)
{
    return sumsquares(x, y);
}
Omnifarious
  • 54,333
  • 19
  • 131
  • 194
  • What's it supposed to do? –  Jun 23 '18 at 23:48
  • @NeilButterworth - I want to be able to express computing the sum of the squares of an arbitrary number of number-like objects with a succinct function-call like syntax. Each time I need this sum, I do know how many numbers I'll be working with at that particular time. And I want the compiler to generate code for this that's as efficient as possible. :-) – Omnifarious Jun 23 '18 at 23:51
  • @Omnifarious I think the right place for your question is: https://codereview.stackexchange.com/ :) – eyllanesc Jun 23 '18 at 23:57
  • @eyllanesc - Yeah, I was wondering if that wasn't a better place. :-/ – Omnifarious Jun 24 '18 at 00:01
  • 2
    You can smash the arguments into an array of their common type and use a conventional loop over that to compute the result. – T.C. Jun 24 '18 at 00:10
  • @eyllanesc - I think I've re-written the question to be more appropriate for StackOverflow. And I also re-worded the question such that I suspect people will more commonly find it to be a useful search result. – Omnifarious Jun 27 '18 at 16:19

3 Answers3

9
#include <utility>
#include <functional>

template<class F, class A0>
auto fold(F&&, A0&& a0) {
    return std::forward<A0>(a0);
}

template<class F, class A0, class...As>
auto fold(F&& f, A0&&a0, As&&...as) {
    return f(std::forward<A0>(a0), fold(f, std::forward<As>(as)...));
}

auto sum_squares=[](auto&&...args) {
    return fold(std::plus<>{}, (args * args)... );
};
Omnifarious
  • 54,333
  • 19
  • 131
  • 194
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • I think not quite right. `return f(std::forward(a0),fold(std::forward(as)...));` should read `return f(std::forward(a0),fold(std::forward(f), std::forward(as)...));`. But, otherwise, this is my new favorite. It provides a way to translate almost any case fold expression into C++14. – Omnifarious Jun 24 '18 at 01:18
  • 1
    @omni sorry, was typing it during opening credits of a movie; didn't compile it. Will try to fix it. Ok fixed; movie over. Should be right or left fold, but I didn't want to be wrong, and would need to examine definitions to be certain. – Yakk - Adam Nevraumont Jun 24 '18 at 03:22
  • Does this solution pattern have a name? – Silicomancer Apr 26 '20 at 00:02
  • @solico Can you guess its name from the function name? – Yakk - Adam Nevraumont Apr 26 '20 at 13:52
8

A possible not-recursive way to write sum() is the following

template <typename ... Ts>
auto sum (Ts ... ts)
 {
   using unused = int[];

   std::common_type_t<Ts...>  ret {};

   (void)unused{ 0, (ret += ts, 0)... };

   return ret;
 }

Given a sum() function, sum_of_squares() can be simply written as follows

template <typename ... Ts>
auto sum_of_squares (Ts ... ts)
 { return sum( (ts*ts)... ); }
max66
  • 65,235
  • 10
  • 71
  • 111
  • One problem with this is the return type. It assumes that the type of the first argument is the appropriate return type, which it may not be in the case of heterogeneous types. – Omnifarious Jun 24 '18 at 00:39
  • 1
    @Omnifarious - you're right; what about using `std::common_type`? Answer modified. – max66 Jun 24 '18 at 00:49
  • 1
    I think your method to avoid recursion is definitely interesting (that's also the first time I see it), however [it seems to lead to less optimal assembly](https://godbolt.org/g/5a584j), and personally I find it a bit less readable – Synxis Jun 24 '18 at 13:48
  • @Synxis - it's a very common method, specially developing C++14 `constexpr` variadic template functions (in C++11 can't be used for `constexpr` functions, in C++17 if often substituted with template folding). But I'm agree: it's a bit less readable. Regarding optimal assembly... interesting; I didn't know. But, maybe, this can depend from specific compiler. – max66 Jun 24 '18 at 16:28
  • I don't highly value removal of recursion as a path to readability. I find Haskell to be completely unreadable, but that's because of the complete lack of any kind of punctuation, not because of the recursion. I find Scheme fairly readable, and it uses recursion just as much. I think learning to read recursive things is a core skill that should be expected of programmers at intermediate and above skill levels. – Omnifarious Jun 26 '18 at 17:58
4

Yes, you can do simpler and more generic actually, by defining a generic sum over several objects:

template<typename T>
T sum(T x)
{
    return x;
}
template<typename U, typename... T>
auto sum(U x, T... nums)
{
    return x + sum(nums...);
}

template<typename... T>
auto sum_of_squares(T... nums)
{
    auto square = [](auto x) { return x * x; };
    return sum(square(nums)...);
}

Live demo here, and here you can see the generated assembly (which seems optimal). Although I personally really dislike auto as return type, here it allows to avoid a very complex expression to get the type.

Synxis
  • 9,236
  • 2
  • 42
  • 64
  • 1
    That is definitely better. Thank you. – Omnifarious Jun 24 '18 at 00:06
  • And I agree with you about `auto`. But, part of the reason I wrote the original C++17-only fold expression-based lambda was to see how many times I could use `auto` in an expression and have it actually be the right thing to do. :-) – Omnifarious Jun 24 '18 at 00:11