Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::max behaves unexpected with uniform initialization and constexpr int

I am experimenting with std::max. I am tring to pass integers constexpr with uniform initialization (curly braces), to compare them to floating-point variables.

Experiment a): Call std::max() with double/int mixed

    double a = 3.0;
    int b = 5;
    auto res = std::max(a, b);

Does not compile. Clang reports error: no matching function for call to 'max'. This is of course OK.

Experiment b): Use curly braces + constexpr int for non-anrrowing conversion

    double a = 3.0;
    constexpr int b = 5;
    auto res = std::max(a, {b});

Compiles and works as expected: returns a double with value 5.0.

Experiment c): Same as b) but swap arguments of std::max.

    double a = 3.0;
    constexpr int b = 5;
    auto res = std::max({b}, a);

Does not compile both under gcc and clang. Why?

Clang reports error: called object type 'double' is not a function or function pointer.

like image 550
CN85 Avatar asked Apr 12 '20 15:04

CN85


2 Answers

There exists an overload of std::max that looks like this (from cppreference.com):

template< class T, class Compare >
constexpr T max( std::initializer_list<T> ilist, Compare comp );

This is a better match for your call auto res = std::max({b}, a); than

template< class T >
constexpr const T& max( const T& a, const T& b );

which you are trying to call, since the {b} can be deduced to std::initializer_list<int> and that call has exact match conversion rank in both arguments, while the overload that you want to call requires a conversion from int to double which is not exact match.

The second argument is then considered the Compare functor to be called for the comparison operation, but calling a double fails obviously. The overload is not disabled if the second argument is not callable, which is why it is still chosen.

This doesn't happen with auto res = std::max(a, {b});, because there is no overload with std::initializer_list parameter for the second argument and so only the overload that you want to call is viable. The initializer list makes the second parameter a non-deduced context, which is why it works in contrast to auto res = std::max(a, b);, which fails due to template argument deduction mismatch between the two arguments.

like image 131
walnut Avatar answered Oct 16 '22 19:10

walnut


In your third 'experiment', you have an initializer list as the first argument to std::max; thus, the compiler is attempting to use the following template (see cppreference):

template< class T, class Compare >
T max( std::initializer_list<T> ilist, Compare comp );

in which the second argument needs to be a comparison function.

like image 41
Adrian Mole Avatar answered Oct 16 '22 20:10

Adrian Mole