Extend number of arguments in std::hypot I would like to call std::hypot with many arguments (not just 2 or 3).

Mandatory requirements:


fast
run at compile time when possible


Non-mandatory requirement:


avoid overflow




#include &lt;cmath&gt;
#include &lt;iostream&gt;


template &lt;typename T&gt;
constexpr T sum2(const T v) {
  return v * v;
}

template&lt;typename T1, typename... Ts&gt;
constexpr T1 sum2(const T1 v1, const Ts... rest)
{
  return v1 * v1 + sum2(rest...);
}

template &lt;typename T1, typename... Ts&gt;
constexpr T1 qsum(const T1 v1, const Ts... v)
{
  return sqrt(sum2(v1, v...));
}


int main()
{
  double a = 10.;
  std::cout &lt;&lt; qsum(1., a) &lt;&lt; std::endl;
}


One bad thing is when I ask qsum(1, 1.) since the first argument is int and so the output. I would like that in this case the inputs are cast to the safer common type.
First of all, let me just say that qsum is a really bad name for a generic hypot function. I would have called it... hypot! In fact, I'm going to do that in this answer, because it'll make the code a lot easier to read.

Well, I'll call it my_hypot, because we'll be including &lt;cmath&gt;, and I don't want any confusion over whether we're accidentally referring to the standard library's 2-argument hypot.



You have an anti-pattern in your code: you're writing things like T sum2(const T v) instead of T sum2(T v). The former is exactly equivalent to the latter as far as the caller of the function is concerned; but for the implementor of the function, the latter is strictly better:


It's less typing, and more importantly, less reading for the maintainer/reviewer.
It doesn't inhibit move semantics.
For these reasons, it's idiomatic C++; and therefore you should follow the idiom because breaking idioms for no reason is distracting to the maintainer/reviewer.




I think you could simplify your code by "thinking in C++17" and then working backwards to lower the resulting code back down to C++11.

In C++17, with fold-expressions, your function would be just

template&lt;typename... Ts&gt; constexpr auto my_hypot(Ts... vs) {
    return sqrt(( (vs * vs) + ... ));
}


In C++14, though, we don't have fold-expressions, so we're going to have to replace the construct (pattern + ...) with some kind of function call; say, sum(pattern...). So our C++14 code should look like

template&lt;typename... Ts&gt;
constexpr auto my_hypot(Ts... vs) {
    return sqrt(sum( (vs * vs)... ));
}


In C++11, we need to use the "trailing decltype" trick to get the compiler to accept that code, since C++11 can't deduce return types (except in lambdas).

template&lt;typename... Ts&gt;
constexpr auto my_hypot(Ts... vs) -&gt; decltype(sqrt(sum( (vs * vs)... ))) {
    return sqrt(sum( (vs * vs)... ));
}


And there we go! Other than writing the sum function, this is a very straightforward translation of the code you want to write, into code that should be acceptable to a C++11 compiler.



However, I think your code as it stands is a bit sketchy, because of the overflow issue. my_hypot(65536, 65536, 1.0) should yield 92681.9, not undefined behavior! To mitigate that issue, I agree with you that casting all of the parameters to common_type would be a good idea. Let's implement that.

#include &lt;cmath&gt;
#include &lt;iostream&gt;
#include &lt;type_traits&gt;

template&lt;typename CT&gt; constexpr CT sum() { return {}; }

template&lt;typename CT, typename T, typename... Ts&gt;
constexpr CT sum(T v, Ts... vs)
{
    return v + sum&lt;CT&gt;(vs...);
}

template &lt;typename... Ts&gt;
constexpr auto my_hypot(Ts... vs) -&gt; typename std::common_type&lt;Ts...&gt;::type
{
    using CT = typename std::common_type&lt;Ts...&gt;::type;
    return sqrt(sum&lt;CT&gt;( (CT(vs) * CT(vs))... ));
}

int main()
{
  std::cout &lt;&lt; my_hypot(10., 10) &lt;&lt; std::endl;
  std::cout &lt;&lt; my_hypot(10, 10, 10.) &lt;&lt; std::endl;
  std::cout &lt;&lt; my_hypot(10, 10) &lt;&lt; std::endl;
}


This is all the same straightforward code as before, with the possible exception of sum&lt;CT&gt;(). Here we're making an overload set of sum&lt;CT&gt;() (with no arguments) and sum&lt;CT&gt;(T, Ts...) (with at least one argument). By construction, both T and all the elements of Ts... will always be CT; we could static_assert that, if we wanted to. sum&lt;CT&gt;() returns a value-initialized CT (i.e., the "zero" of whatever type CT happens to be); I used {} instead of 0 just in case CT is some wacky bignum type that doesn't have an implicit conversion from int. Depending on your assumptions, 0 might be the safer choice.



The really nice thing about this code is that because we constructed it by starting with C++17 and moving backward, it's easy to take this code and migrate it forward to C++14 (by removing the trailing return types) and then to C++17 (by replacing the implementation of sum with a one-liner, or inlining it altogether) as your compiler catches up. Looking at your original code, I think it would be a little bit harder to disentangle the C++11isms from the "business logic" of how to compute hypot. For example, you've got the subexpression v * v repeated in two different places, for no good reason that I can see. You could have factored that out... or rather, not factored it in in the first place! :) Don't repeat yourself.