如何找到变量数量的constexpr std::arrays的constexpr最大值,带有默认值。

3

所以,我有许多constexpr std::array<int, N>,针对不同的N值。在这种情况下:

constexpr std::array<int, 3> r1 {1, 3, 5};
constexpr std::array<int, 2> r2 {3, 4};
constexpr std::array<int, 4> r3 {1, 2, 5, 6};
constexpr std::array<int, 2> r4 {2, 6};

我想要做的是找到所有array中的constexprmax(以及随后的min)元素。这似乎可以正常工作:

constexpr int the_max() {
    return 0;
}

template<typename T, typename... Ts>
constexpr int the_max(T&& t, Ts&&... ts) {
    const int v = *std::max_element(t.cbegin(), t.cend());
    return std::max(v, the_max(ts...));
}

如下所示:

constexpr auto max_entry = dlx::the_max(r1, r2, r3);
std::cout << max_entry << '\n';

打印输出结果为6,这是预期的。

然而,感觉应该有更多的逻辑,例如:

  1. 默认值或最小值;以及

  2. std::array 中的类型应该可以不同,只要它们都是算术类型。

我认为下面的代码也应该有效:

template<typename B>
constexpr std::enable_if_t<std::is_arithmetic_v<B>, B>
the_max2(B&& b) {
    return b;
}

template<typename B, typename T, typename... Ts>
constexpr std::enable_if_t<std::is_arithmetic_v<B> && std::is_arithmetic_v<T::value_type>, std::common_type_t<B, typename T::value_type>>
the_max2(B&& b, T&& t, Ts&&... ts) {
    const int v = *std::max_element(t.cbegin(), t.cend());
    return std::max(v, the_max2(ts...));
}

但是它会出现错误:

error: no matching function for call to 'the_max2<int>(int, const std::array<int, 3>&, const std::array<int, 2>&, const std::array<int, 4>&)'

只期望1个参数,但收到了4个参数,并且:

 error: 'value_type' is not a member of 'const std::array<int, 3>&'

有人能告诉我我做错了什么吗?非常感谢您的帮助。


1
看起来你在这里忘记了关键字 typename,应该是 std::is_arithmetic_v<typename T::value_type> - 273K
2个回答

1

一些问题,没有特定的顺序

(1) 正如S.M.指出的那样,在T::value_type之前你忘记了typename

template<typename B, typename T, typename... Ts> // .......................VVVVVVVV
constexpr std::enable_if_t<std::is_arithmetic_v<B> && std::is_arithmetic_v<typename T::value_type>, std::common_type_t<B, typename T::value_type>>

(2) 在递归调用中你忘记了b

// .........................V
return std::max(v, the_max2(b, ts...));

(3) 当你应该使用 auto (或者 typename T::value_type, 如果你更喜欢) 时,你使用了 int 类型来定义 v

// ...VVVV (not int)
const auto v = *std::max_element(t.cbegin(), t.cend());

(4) "common type" 应该也要评估 Ts::value_type,因此

// ...........................................VVVVVVVVVVVVVVVVVVVVVVVVVV
std::common_type_t<B, typename T::value_type, typename Ts::value_type...>

(5) 你应该为 std::max() 明确指定类型。

   using rt = std::common_type_t<B, typename T::value_type, typename Ts::value_type...>;
   // ...
   return std::max<rt>(v, the_max2(b, ts...));
   // ............^^^^

(6) 我建议将参数作为const指针而不是rvalue接收

 //..........VVVVVVV......VVVVVVV.......VVVVVVV
 the_max2 (B const & b, T const & t, Ts const & ... ts)

以下是一个完整的编译示例(为了检测仅一次返回的公共类型进行了简化)
#include <array>
#include <iostream>
#include <algorithm>
#include <type_traits>

template <typename B>
constexpr std::enable_if_t<std::is_arithmetic<B>::value, B>
   the_max2 (B const & b)
 { return b; }


template <typename B, typename T, typename ... Ts,
          typename RT = std::common_type_t<B, typename T::value_type,
                                           typename Ts::value_type...>>
constexpr std::enable_if_t<std::is_arithmetic<B>::value
                        && std::is_arithmetic<typename T::value_type>::value, RT>
   the_max2 (B const & b, T const & t, Ts const & ... ts)
 {
   const auto v = *std::max_element(t.cbegin(), t.cend());
   return std::max<RT>(v, the_max2(b, ts...));
 }

int main()
 {
   constexpr std::array<short, 3> r1 {{1, 3, 5}};
   constexpr std::array<int, 2> r2 {{3, 4}};
   constexpr std::array<long, 4> r3 {{1, 2, 5, 6}};
   constexpr std::array<long long, 2> r4 {{2, 6}};

   auto  m { the_max2(4l, r1, r2, r3, r4) };

   std::cout << m << std::endl;
 }

额外建议:如果您可以放弃对std::is_arithmetic的测试,您就不需要递归,您可以简单地扩展可变模板来编写函数,如下所示

template <typename B, typename ... Ts,
          typename RT = std::common_type_t<B, typename Ts::value_type...>>
constexpr RT the_max3 (B const & b, Ts const & ... ts)
 { return std::max<RT>({b, *std::max_element(ts.cbegin(), ts.cend())...}); }

如果您可以使用C++17而不是C++14,您可以使用模板折叠来恢复std::is_arithmetic SFINAE测试,如下所示。
template <typename B, typename ... Ts,
          typename RT = std::common_type_t<B, typename Ts::value_type...>>
constexpr std::enable_if_t<
     (std::is_arithmetic<B>::value && ...
   && std::is_arithmetic<typename Ts::value_type>::value), RT>
   the_max3 (B const & b, Ts const & ... ts)
 { return std::max<RT>({b, *std::max_element(ts.cbegin(), ts.cend())...}); }

问题是:是否有理由更喜欢std::is_arithmetic<X>::value而不是std::is_arithmetic_v<X>?感谢提供模板折叠示例。我还没有尝试过模板折叠,这将是我优秀的学习材料。(另外,很抱歉我犯了愚蠢的错误,也感谢您指出!) - Sebastian
1
@Sebastian - 抱歉,std::is_arithmetic<X>::value 是我的编译器的问题:它太旧了,不支持 std::is_arithmetic_v<X> - max66
对于您的第一个奖励建议,您是否认为将返回类型从 RT 更改为以下内容有什么问题:std::enable_if_t<std::is_arithmetic_v<RT>, RT>?这样,我可以通过检查公共类型来保留 is_arithmetic 吗?或者那样做是没有意义的? - Sebastian
@Sebastian - 是的:我认为应该可以工作;我现在看到这是一个双重SFINAE过滤器;第一个过滤器在RT模板类型本身上,第二个过滤器在返回值上。就我记得以往从未做过类似的事情(在C++17折叠情况下除外),但我不明白为什么不能工作。 - max66

1
你需要将std::is_arithmetic特性应用于传递参数的value_type而不是它们自己,还需要从模板参数中删除引用,因为你正在使用转发引用。
使用
namespace impl {
    template <bool... Preds> struct all_dummy;
    template <bool... Preds> using all = std::is_same<all_dummy<Preds...>, all_dummy<((void)Preds, true)...>>;
}

template<typename T, typename... Ts>
constexpr std::enable_if_t<
    impl::all<
        std::is_integral<typename std::remove_reference_t<T>::value_type>::value
    >::value,
    typename std::remove_reference_t<T>::value_type
>
the_max2(T&& t) {
    const int v = *std::max_element(t.cbegin(), t.cend());
    return v;
}


template<typename T, typename... Ts, typename R = 
    std::common_type_t<
            typename std::remove_reference_t<T>::value_type,
            typename std::remove_reference_t<Ts>::value_type...>
>
constexpr std::enable_if_t<
    impl::all<
        std::is_integral<typename std::remove_reference_t<T>::value_type>::value,
        std::is_integral<typename std::remove_reference_t<Ts>::value_type>::value...
    >::value,
    R
>
the_max2(T&& t, Ts&&... ts) {
    const int v = *std::max_element(t.cbegin(), t.cend());
    return std::max<R>(v, the_max2(ts...));
}

如果可用,则可以使用if constexpr简化代码。
namespace impl {
    template <bool... Preds> struct all_dummy;
    template <bool... Preds> using all = std::is_same<all_dummy<Preds...>, all_dummy<((void)Preds, true)...>>;
}


template<typename T, typename... Ts, typename R =
    std::common_type_t<
        typename std::remove_reference_t<T>::value_type,
        typename std::remove_reference_t<Ts>::value_type...>
>
constexpr std::enable_if_t<
    impl::all<
        std::is_integral_v<typename std::remove_reference_t<T>::value_type>,
        std::is_integral_v<typename std::remove_reference_t<Ts>::value_type>...
    >::value,
    R
>
the_max2(T&& t, Ts&&... ts) {
    const int v = *std::max_element(t.cbegin(), t.cend());
    if constexpr (sizeof...(ts) > 0) {
        return std::max<R>(v, the_max2(ts...));
    } else {
        return v;
    }
}

1
不错,但是...这个问题被标记为C++14;if constexpr 仅在C++17中可用(与允许更大简化的模板折叠一起)。 - max66
1
@Sebastian - 好的;所以 if constexpr 是可用的。 - max66
1
是的,抱歉我没有注意到标签。我已经为c++14修复了它。 - Jans
1
@Jans - 不, 请阅读原帖的评论:他接受C++17并更改了标签。 - max66
1
@Sebastian impl::all 仅用于将包 Preds 展开为 all_dummy 的参数,以便您可以使用它来检查所有 Preds 是否为真,all_dummy<true, false> != all_dummy<true, true> 在这里检查一些测试 here。强制转换 (void)Preds 只是为了避免未使用表达式的警告。请注意,在 C++17 中不真正需要 impl::all 的技巧,因为您有折叠表达式,您可以像 @max66 在他的答案中所做的那样使用它。 - Jans
显示剩余4条评论

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