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

Implementing variadic Max function in C++20

提问人:MyClass 提问时间:7/10/2023 最后编辑:JeJoMyClass 更新时间:8/8/2023 访问量:256

问:

尽管如此,事实上,我们有,我想尝试是否可以制作一个采用可变参数并以递归方式调用 以查找 max 元素的版本。std::maxMaxMax

我在 stack overflow 中看到了类似的帖子,但这些帖子很旧,而且大多数都在里面使用。由于我有一个特定的错误并且使用较新的编译器,因此这篇文章不容易复制。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));
  • 同样,我也觉得我正在写更多内容来实现 中的这个功能。有人有什么建议吗 将两个功能合二为一?MaxMax
++20 C ++ variadic-templates 函数模板 C++17

评论

1赞 Ted Lyngmo 7/10/2023
你真的是说而不是吗?Max("string1", "string2"s)Max("string1"s, "string2"s)
0赞 MyClass 7/10/2023
@TedLyngmo是的,我想要,和,并且可能对于所有那些小于或大于运算符定义的那些。std::stringconst char*std::string_view
2赞 Ted Lyngmo 7/10/2023
明白了。然后,您需要处理仅提供的情况。const char*

答:

13赞 JeJo 7/10/2023 #1

同样,我也觉得我正在写更多内容以在 中实现此功能 [...] ?Max

您的函数可以通过以下方式最大限度地提高简单性Max

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在评论部分指出的那样,如果您只传递连续的 s(字符串文字),则上述方法不起作用。例如,场景const char*

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

因为这会产生指针比较,而不是您想要的比较。您最初显示的代码也是如此。您可能需要单独处理这种情况。

例如,在下面的代码示例中,如果 是可转换为 ,我们将其转换为并执行更大的检查:valuestd::string_viewstd::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))Tconst char[8]"string1"std::string"string2"s

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

现在必须将其隐式转换为 。这是不可行的,因此编译器会产生类型不匹配错误。std::stringconst char [8]

要解决这个问题,你可以简单地让编译器为你推断类型;这意味着,与其定义或假设 返回类型将始终为 ,使用,以便编译器可以为您推断类型。Tauto

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

评论

1赞 MyClass 7/10/2023
哇,这看起来很简单。你能给我参考,在哪里可以找到使用的这些功能吗?
1赞 Jarod42 7/10/2023
此外,还可以使用 / 来处理不相关的指针比较。std::lessstd::greater
4赞 Ted Lyngmo 7/10/2023
@JeJo 就是那些的指针:-)也许捕捉并将它们变成 s 将是一种选择char*string_view
2赞 JeJo 7/10/2023
@TedLyngmo 感谢您提出这个问题。我忘记了甚至有同样的问题:为什么 std::max 不适用于字符串文字?我已经按照..:)std::max
1赞 chris 7/10/2023
@JeJo,你是对的,我忘记并错过了它有涵盖这种情况的核心三元专业化,几乎这就是common_type ;)
4赞 JeJo 7/11/2023 #2

同样,我也觉得我正在编写更多内容以在 中实现此功能 [...]?Max

作为对另一个答案的扩展,使用 fold 表达式,也可以使 成为非递归的。Max

#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 现场演示