Search code examples
c++templatesc++20variadic-templatesfunction-templates

Implementing variadic Max function in C++20


Despite, the fact, we have std::max, I wanted to try if it is possible to make a Max version that takes variadic arguments and calls the Max recursively for finding the max element.

I saw similar posts in stack overflow, but those are old and most of them use std::max inside. Since I have a specific error and using a newer compiler, this post is not duplicated easily.

Following is the code I have written:

#include <iostream>
#include <string>
#include <format>
using namespace std::string_literals;

template <typename T>
constexpr T Max(T&& value)
{  
  return value;
}

template <typename T, typename... Ts>
constexpr T Max(T&& value, Ts&&... args)
{
    const T maxRest = Max(args...);

    return (value > maxRest) ? value : maxRest;
}

int main()
{
    std::cout << std::format("Maximum integer: {}\n", Max(1));
    std::cout << std::format("Maximum integer: {}\n", Max(5, 2, 10, 6, 8));
    std::cout << std::format("Maximum integer: {}\n", Max("string1", "string2"s));  // error in this line!!
    std::cout << std::format("Maximum double: {}\n", Max(3.14, 1.23, 2.56, 0.98));
    return 0;
}

For which I am getting:

main.cc(79, 21) : error C2440 : 'initializing' : cannot convert from 'std::string' to 'const char (&)[8]'
main.cc(79, 21) : message: Reason: cannot convert from 'std::string' to 'const char [8]'
main.cc(79, 21) : message: No user - defined - conversion operator available that can perform this conversion, or the operator cannot be called
main.cc(87, 55) : message: see reference to function template instantiation 'T Max<const char(&)[8],std::string>(T,std::string &&)' being compiled
with
[
    T = const char(&)[8]
]
  • I think the error is coming from the function call: Max("string1", "string2"s));. I do not know, how can resolve this.
  • Likewise, I am also feeling that I am writing more to achieve this Max function in . Does anybody have any suggestion to make the two Max functions into one?

Solution

  • Likewise, I am also feeling that I am writing more to achieve this Max function in [...] ?

    Your Max function can maximize the simplicity by

    constexpr auto Max(auto const& value, auto const&... args)
    {
        if constexpr (sizeof...(args) == 0u) // Single argument case!
            return value;
        else // For the Ts...
        {
            const auto max = Max(args...);
            return value > max ? value : max;
        }
    }
    

    See live demo in godbolt.org

    Update: As pointed out in the comment section by @TedLyngmo, the above does not work if you only pass consecutive const char*s (string literals). Ex. scenario

    Max("string1"s, "string2", "string4", "string3") // result is "string2" instead of "string4"
    

    Because this is resulting a pointer comparison rather than the comparison you want. It was the case also for your original shown code. You might want to handle this situation separately.

    For instance, in the following code example, if the value is convertible to std::string_view, we convert it to std::string_view and do the greater check:

    #include <type_traits>  // std::is_convertible
    
    constexpr auto Max(auto const& value, auto const&... args)
    {
        if constexpr (sizeof...(args) == 0u) // Single argument case!
        {
            if constexpr (std::is_convertible_v<decltype(value), std::string_view>)
                return std::string_view{value};
            else
                return value;
        }
        else // For the Ts...
        {
            const auto max = Max(args...);
            return value > max ? value: max;
        }
    }
    

    See live demo in godbolt.org

    Likewise, each time when you use this Max function, always remember to check whether the passed argument is some type of pointer, which is clearly not handled by it.


    I think the error is coming from the function call: Max("string1", "string2"s)); . I do not know, how can to resolve this.

    When you call Max("string1", "string2"s)), the compiler deduces the T (i.e. return type), to be const char[8], that is the type of the first argument(i.e. "string1"). However, the second argument is a std::string (i.e. "string2"s). Now for the line :

    const T maxRest = Max(args...);
    

    this std::string must be now implicitly convertible to const char [8].This is not viable, and hence the compiler produces a type mismatch error.

    To fix the issue, you can simply let the compiler deduce the type for you; That means, instead of defining or assuming the the return type will be always T, use auto so that compiler can deduce the type for you.

    template <typename T, typename... Ts>
    constexpr auto Max(T const& value, Ts const&... args)
    //        ^~~~ ---> Simply 'auto'
    {
        const auto maxRest = Max(args...);
        //    ^~~~ ---> Simply 'auto'
        return (value > maxRest) ? value : maxRest;
    }
    

    See live demo in godbolt.org

    Alternatively, you could also use the std::common_type_t for defining the return type.

    #include <type_traits> // std::common_type_t
    
            template <typename T, typename... Ts>
    constexpr auto Max(T const& value, Ts const&... args)
    -> std::common_type_t<T, Ts...>
    {
        // ....
    }