在C++20中实现可变参数的最大函数。

9
尽管我们有std::max,但我想尝试一下是否可以创建一个接受可变参数并递归调用Max函数来找到最大元素的Max版本。
我在stackoverflow上看到了类似的帖子,但那些帖子都比较旧,并且大多数都在内部使用std::max。由于我遇到了特定的错误,并且使用了更新的编译器,所以这篇帖子不容易重复。
以下是我编写的代码:
#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;
}

我得到的是:
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]
]

我觉得错误是来自函数调用:Max("string1", "string2"s));。我不知道该怎么解决。
同样,我也感觉我写了很多来实现这个中的Max函数。有人有什么建议可以将这两个Max函数合并成一个吗?

1
你是真的想要 Max("string1", "string2"s) 而不是 Max("string1"s, "string2"s) 吗? - Ted Lyngmo
1
你是真的指的 Max("string1", "string2"s) 而不是 Max("string1"s, "string2"s) 吗? - Ted Lyngmo
1
你是真的指的Max("string1", "string2"s)而不是Max("string1"s, "string2"s)吗? - undefined
2
我明白了。那么你需要处理只提供 const char* 的情况。 - Ted Lyngmo
2
我明白了。那么你需要处理只提供const char*的情况。 - Ted Lyngmo
显示剩余2条评论
2个回答

13
同样地,我也感觉到为了实现这个中的Max函数而写得更多了吗?
你的Max函数可以通过最大化简洁性来实现。
具有缩写函数模板(自起)声明,以及 使用if constexpr(自起)进行编译时分支。
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;
    }
}

在godbolt.org上查看实时演示

更新: 正如@TedLyngmo在评论部分指出的那样,如果您只传递连续的const char*(字符串字面值),上述方法将无法正常工作。例如,情况

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

因为这会导致指针比较,而不是你想要的比较。对于你原先展示的代码也是如此。你可能希望单独处理这种情况。
例如,在下面的代码示例中,如果value可以转换为std::string_view,我们将其转换为std::string_view并进行更大的检查:
#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;
    }
}

在godbolt.org上查看实时演示

同样,每次使用这个Max函数时,都要记得检查传递的参数是否是某种类型的指针,因为它明显没有处理这种情况。


我认为错误来自于函数调用:Max("string1", "string2"s));。 我不知道如何解决这个问题。
当你调用Max("string1", "string2"s))时,编译器推断出T(即返回类型)为const char[8],这是第一个参数的类型(即"string1")。然而,第二个参数是一个std::string(即"string2"s)。现在看看这一行:
const T maxRest = Max(args...);

这个 `std::string` 现在必须能够隐式转换为 `const char [8]`。这是不可行的,因此编译器会产生类型不匹配的错误。
要解决这个问题,你可以简单地让编译器为你推断类型;也就是说,不要定义或假设返回类型总是 `T`,而是使用 `auto`,这样编译器可以为你推断类型。
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;
}

在godbolt.org上查看实时演示

或者,您还可以使用std::common_type_t来定义返回类型。

#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...>
{
    // ....
}

4
@JeJo 这就是那些可恶的指针 :-) 或许捕获char*并将其转换为string_view可能是一个选择。 - Ted Lyngmo
4
@ JeJo 那些可恶的指针 :-) 或许捕捉 char* 并将其转换为 string_view 是一个选项。 - Ted Lyngmo
4
@JeJo 这些该死的指针 :-) 或许可以捕捉char*并将其转换为string_view,这可能是一个选择。 - undefined
2
@TedLyngmo 感谢你提出这个问题。我忘记了即使是 std::max 也有相同的问题:为什么对于字符串字面值,std::max 不起作用?。我已经根据...进行了更新 :) - JeJo
2
@TedLyngmo 谢谢你提出这个问题。我忘记了即使是std::max也有同样的问题:为什么对于字符串字面值,std::max不起作用?。我已经根据...进行了更新 :) - JeJo
显示剩余28条评论

4
同样地,我也感觉我正在写更多的代码来实现这个中的Max函数呢?作为对其他答案的扩展,使用fold expressionsMax也可以变得非递归。
#include <type_traits> // std::common_type, std::remove_cvref
#include <functional>  // std::greater

template<typename... T>  // common type helper
using CommonType = std::common_type_t<std::remove_cvref_t<T>...>;

constexpr auto Max(auto const& value, auto const&... args)
{
    CommonType<decltype(value), decltype(args)...> maxVal = value;

    return sizeof...(args) == 0u ? maxVal 
        : (((maxVal = std::greater{}(args, maxVal) ? args : maxVal), ...)
            , maxVal);
}

在godbolt.org上的实时演示


然而,对于连续的字符串字面量,这需要一些额外的处理:
template<typename... T>  // common type helper
using CommonType = std::common_type_t<std::remove_cvref_t<T>...>;

// For string literals comparison.
constexpr auto handleStrLiterals(auto const& t)
{
    if constexpr (std::is_convertible_v<decltype(t), std::string_view>)
            return std::string_view{ t };
    else    return t;
};

constexpr auto Max(auto const& value, auto const&... args)
{
    CommonType<decltype(handleStrLiterals(value)), decltype(args)...>
        maxVal = handleStrLiterals(value);
    return sizeof...(args) == 0u ? maxVal 
        : (((maxVal = std::greater{}(args, maxVal) ? args : maxVal), ...)
            , maxVal);
}

在godbolt.org上的实时演示


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接