gpt4 book ai didi

c++ - 模板函数重载和 SFINAE 实现

转载 作者:塔克拉玛干 更新时间:2023-11-03 00:32:54 24 4
gpt4 key购买 nike

我正在花一些时间学习如何在 C++ 中使用模板。我从没用过之前,我并不总是确定在不同情况下可以实现什么或不能实现什么。

作为练习,我包装了我在事件中使用的一些 Blas 和 Lapack 函数,我目前正在包装 ?GELS (计算线性方程组的解)。

 A x + b = 0

?GELS函数(仅适用于实数值)有两个名称:SGELS , 对于单精度 vector 和 DGELS double 。

我对界面的想法是一个函数solve这样:

 const std::size_t rows = /* number of rows for A */;
const std::size_t cols = /* number of cols for A */;
std::array< double, rows * cols > A = { /* values */ };
std::array< double, ??? > b = { /* values */ }; // ??? it can be either
// rows or cols. It depends on user
// problem, in general
// max( dim(x), dim(b) ) =
// max( cols, rows )
solve< double, rows, cols >(A, b);
// the solution x is stored in b, thus b
// must be "large" enough to accomodate x

根据用户要求,问题可能是多定的或未定的,这意味着:

  • 如果多定dim(b) > dim(x) (解是伪​​逆)
  • 如果不确定dim(b) < dim(x) (解决方案是 LSQ 最小化)
  • 或正常情况下 dim(b) = dim(x) (解是 A 的逆)

(不考虑个别情况)。

?GELS将结果存储在输入 vector 中 b , std::array应该有足够的空间来容纳解决方案,如代码注释 (max(rows, cols)) 中所述。

我想(编译时)确定采用哪种解决方案(这是一个参数更改在 ?GELS称呼)。我有两个功能(为了这个问题我正在简化),处理精度并且已经知道哪个是 b 的维度和 rows 的数量/cols :

namespace wrap {

template <std::size_t rows, std::size_t cols, std::size_t dimb>
void solve(std::array<float, rows * cols> & A, std::array<float, dimb> & b) {
SGELS(/* Called in the right way */);
}

template <std::size_t rows, std::size_t cols, std::size_t dimb>
void solve(std::array<double, rows * cols> & A, std::array<double, dimb> & b) {
DGELS(/* Called in the right way */);
}

}; /* namespace wrap */

是内部包装器的一部分。用户函数,确定所需的大小在b通过模板 vector :

#include <type_traits>

/** This struct makes the max between rows and cols */
template < std::size_t rows, std::size_t cols >
struct biggest_dim {
static std::size_t const value = std::conditional< rows >= cols, std::integral_constant< std::size_t, rows >,
std::integral_constant< std::size_t, cols > >::type::value;
};

/** A type for the b array is selected using "biggest_dim" */
template < typename REAL_T, std::size_t rows, std::size_t cols >
using b_array_t = std::array< REAL_T, biggest_dim< rows, cols >::value >;

/** Here we have the function that allows only the call with b of
* the correct size to continue */
template < typename REAL_T, std::size_t rows, std::size_t cols >
void solve(std::array< REAL_T, cols * rows > & A, b_array_t< REAL_T, cols, rows > & b) {
static_assert(std::is_floating_point< REAL_T >::value, "Only float/double accepted");
wrap::solve< rows, cols, biggest_dim< rows, cols >::value >(A, b);
}

以这种方式它确实有效。但是我想更进一步,我真的不知道该怎么做。如果用户尝试调用 solveb如果尺寸太小,编译器会引发极其难以阅读的错误。

我正在尝试插入一个static_assert这有助于用户理解他的错误。但我脑海中出现的任何方向需要使用两个具有相同签名的函数(这就像模板重载?)我找不到 SFINAE 策略(实际上它们根本不编译)。

您认为有可能针对错误的情况提出静态断言吗 b编译时不改变用户界面维度?我希望这个问题足够清楚。

@Caninonos:对我来说,用户界面就是用户调用求解器的方式,即:

 solve< type, number of rows, number of cols > (matrix A, vector b)

这是我为了提高我的技能而对我的锻炼施加的约束。这意味着,我不知道是否真的有可能实现解决方案。 b 的类型必须匹配函数调用,如果我添加另一个模板参数并更改用户界面,这很容易违反我的约束。

最小的完整和工作示例

这是一个最小的完整的工作示例。根据要求,我删除了对线性代数概念的任何引用。是个数的问题。这些案例是:

  • N1 = 2, N2 =2 .自 N3 = max(N1, N2) = 2一切正常
  • N1 = 2, N2 =1 .自 N3 = max(N1, N2) = N1 = 2一切正常
  • N1 = 1, N2 =2 .自 N3 = max(N1, N2) = N2 = 2一切正常
  • N1 = 1, N2 =2 .自 N3 = N1 = 1 < N2它正确地引发了编译错误。我想用一个静态断言来拦截编译错误,该断言解释了 N3 的维度这一事实。是错的。至于现在,错误很难阅读和理解。

您可以 view and test it online here

最佳答案

首先是一些改进,可以稍微简化设计并提高可读性:

  • 不需要 biggest_dim . std::max自 C++14 起为 constexpr。您应该改用它。

  • 不需要 b_array_t .你可以只写 std::array< REAL_T, std::max(N1, N2)>

现在来解决您的问题。 C++17 中的一种好方法是:

template < typename REAL_T, std::size_t N1, std::size_t N2, std::size_t N3>
void solve(std::array< REAL_T, N1 * N2 > & A, std::array< REAL_T, N3> & b) {

if constexpr (N3 == std::max(N1, N2))
wrap::internal< N1, N2, N3 >(A, b);
else
static_assert(N3 == std::max(N1, N2), "invalid 3rd dimension");

// don't write static_assert(false)
// this would make the program ill-formed (*)
}

或者,正如@max66 所指出的

template < typename REAL_T, std::size_t N1, std::size_t N2, std::size_t N3>
void solve(std::array< REAL_T, N1 * N2 > & A, std::array< REAL_T, N3> & b) {

static_assert(N3 == std::max(N1, N2), "invalid 3rd dimension");

if constexpr (N3 == std::max(N1, N2))
wrap::internal< N1, N2, N3 >(A, b);

}

Tadaa!! 简单、优雅、漂亮的错误消息。

constexpr if 版本与 static_assert 之间的区别即:

void solve(...)
{
static_assert(...);
wrap::internal(...);
}

是只有static_assert吗?编译器将尝试实例化 wrap::internal即使在 static_assert失败,污染错误输出。如果调用 wrap::internal,则使用 constexpr在条件失败时不是正文的一部分,因此错误输出是干净的。


(*) 我不写 static_asert(false, "error msg) 的原因是因为这会使程序格式错误,不需要诊断。参见 constexpr if and static_assert


您还可以制作 float/double如果你想通过在不可扣除的参数之后移动模板参数来扣除:

template < std::size_t N1, std::size_t N2, std::size_t N3,  typename REAL_T>
void solve(std::array< REAL_T, N1 * N2 > & A, std::array< REAL_T, N3> & b) {

所以调用变成:

solve< n1_3, n2_3>(A_3, b_3);

关于c++ - 模板函数重载和 SFINAE 实现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49171904/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com