我花一些时间来学习如何在C++中使用模板。我没用过
在此之前,我并不总是确定在不同情况下可以实现或无法实现的目标。
作为练习,我将包装一些用于事件的Blas和Lapack函数,
并且我目前正在研究?GELS
的包装(评估线性方程组的解)。
A x + b = 0
?GELS
函数(仅用于实数值)存在两个名称:SGELS
,用于单精度 vector 和DGELS
具有 double 。我对接口(interface)的想法是通过这种方式实现的
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);
}
这样实际上可以工作。但是我想进一步走下去,而且我真的不知道如何做。
如果用户尝试使用大小过小的
solve
来调用b
,则编译器会引发极其难以阅读的错误。我正在尝试插入
可以帮助用户理解其错误的
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 dimmension");
// 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 dimmension");
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/