0

In my C++14 project I'm using

#include <algorithm>
using std::max;            // for max(a,b)

but I also want to provide a max() function taking any number of arguments (of equal type). To this end, I added

template<typename T, typename...A>
constexpr inline T const& max(T const&x, T const&y, A...&&z)
{
    return max(max(x,y), std::forward<A>(z)...);
}

but upon a call like

double x,y,z;
...
auto m = max(x,y,z);

the compiler tries to instead of my overload use

namespace std {
template< class T, class Compare >
constexpr const T& max( const T& a, const T& b, Compare comp );
}

from algorithm. How can I avoid that (but still provide the intended functionality)?

(Note that there is a question about variadic min/max, but it doesn't address the issue I have by simply changing the name to vmin/vmax.)

Walter
  • 44,150
  • 20
  • 113
  • 196
  • 6
    There is an overload of `std::max` that takes an initializer list. Perhaps that would work for you instead? https://en.cppreference.com/w/cpp/algorithm/max – Retired Ninja Sep 15 '20 at 17:41
  • 3
    Don't do `using std::max` -- use explicit namespace resolution everywhere. – wcochran Sep 15 '20 at 17:47
  • @RetiredNinja this is not a solution, as it does not *provide the intended functionality*. Same applies to wochran's comment. – Walter Sep 15 '20 at 18:35
  • It should be noted that the reason that the std::max overload is being called is because double can implicitly be converted to bool, which makes the overload take higher precedence than unpacking the parameter pack and continuing the expansion – mkamerath Sep 15 '20 at 18:59
  • 1
    @mkamerath why bool? – Oktalist Sep 15 '20 at 19:33
  • Why not put your `max` in its own namespace? – Den-Jason Sep 15 '20 at 22:15
  • @Den-Jason how would that help? – Walter Sep 16 '20 at 06:31
  • @Walter never mind, I tested the answer candidate and it works. I have provided a demonstration for anyone who may be interested. – Den-Jason Sep 16 '20 at 11:56

4 Answers4

4

The problem can be solved by only overloading the function for 3 or more arguments like:

template<typename T, typename...A>
constexpr inline T const& max(T const&x, T const&y, T const&z, A&&...w)
{
    return max(max(x,y), z, std::forward<A>(w)...);
}
yao99
  • 870
  • 5
  • 12
  • To clarify: does this work because this template's specializations (those with `A = []`) are considered to be more specific than the ones for the one in the standard library and is therefore chosen over that one when possible? – HTNW Sep 15 '20 at 19:40
  • @HTNW I think so. And I'm still reading the standard [\[over.match.best\]](https://eel.is/c++draft/over.match.best). – yao99 Sep 15 '20 at 19:51
1

You don't.

Either stop using std::max and qualify your namespaces instead, or name your function something else.

Asteroids With Wings
  • 17,071
  • 2
  • 21
  • 35
0

As stated in the comments, you can simply use std::max which takes an initializer list.

An example call would be:

auto maxNum = std::max({a, b, c});
mkamerath
  • 312
  • 2
  • 12
  • sorry, but this does not meet the criterion stated (*provide the intended functionality*) – Walter Sep 15 '20 at 18:33
  • 2
    @Walter _"this does not meet the criterion stated"_ In what way? – Asteroids With Wings Sep 15 '20 at 18:39
  • according to https://en.cppreference.com/w/cpp/algorithm/max it states that `T must meet the requirements of CopyConstructible in order to use overloads (3,4). ` and I guess that is the reason for not using the initialiser list overload...... and hence Walter's comment about non-copyable objects ;) – Den-Jason Sep 15 '20 at 22:12
  • @AsteroidsWithWings in two ways: (1) I have to change `max(a,b,c)` everywhere in my project -- at the moment this is implemented via an overload, not a variadic template. (2) it will fail for non-copy-constructible objects and will create undesired copies of large objects. – Walter Sep 16 '20 at 06:27
  • @Walter Neither of those criteria were stated in your question. – Asteroids With Wings Sep 16 '20 at 10:58
0

I would suggest yao99's answer should be the accepted one. For sake of curiosity, here I present you with a solution that provides an adaptor through your own namespace. This only ever uses the binary parameter overload of std::max so there is no possibility of the initialiser list overload becoming invoked. The client code can still use max as indicated:

The adaptor:

#include <iostream>
#include <algorithm>

namespace variadicMax
{
    // the variadic "max" recurses and reduces to a binary parameter version that we must implement
    template<typename T>
    constexpr inline T const& max(T const&x, T const&y)
    {
        return (std::max(x, y));
    }

    // the variadic overload recurses itself, removing two parameters (and adding one) each time.
    // Eventually it reduces to (x,y) which is handled by the other overload.
    template<typename T, typename...Args>
    constexpr inline T const& max(T const&x, T const&y, Args&&...z)
    {
        return (max (std::max(x, y), std::forward<Args>(z)...));
    }
};

The client:

// this is the only change needed in clients:
using variadicMax::max;

int main()
{
    double x, y, z, w, v;

    x = 1;  y = 2;  z = 3;  w = 4;  v = 5;
    auto m5 = max(x, y, z, w, v);  std::cout << m5 << std::endl;
    auto m5A = max(x, y, z, 4.0, v);  std::cout << m5A << std::endl;

    x = 1;  y = 2;  z = 3;
    auto m = max(x, y, z);  std::cout << m << std::endl;

    x = 2;  y = 3;  z = 1;
    auto m2 = max(x, y, z);  std::cout << m2 << std::endl;

    x = 3;  y = 2;  z = 1;
    auto m3 = max(x, y, z);  std::cout << m3 << std::endl;

    x = 3;  y = 2;
    auto m4 = max(x, y);  std::cout << m4 << std::endl;

    x = 3;  y = 2;
    auto m6 = max(3, 2);  std::cout << m6 << std::endl;

    x = 3;  y = 2;
    auto m7 = max(x, 2.0);  std::cout << m7 << std::endl;

    etc...
Den-Jason
  • 2,395
  • 1
  • 22
  • 17