我有一个Matrix类模板,如下所示:
template<typename T, std::size_t nrows, std::size_t ncols>
class Matrix
{
T data[nrows][ncols];
public:
T& operator ()(std::size_t i, std::size_t j)
{
return data[i][j];
}
};
我想要的是在编译时.setIdentity()
为nrows==ncols
时仅为实例化定义true
函数。当.setIdentity()
为nrows==ncols
时,false
会有没有定义。
我正在尝试使用enable_if
成语,但这将定义所有情况的函数。不是吗?
答案 0 :(得分:8)
只需添加部分专业化:
template<typename T, std::size_t N>
class Matrix<T, N, N>
{
T data[N][N];
public:
T& operator ()(std::size_t i, std::size_t j)
{
return data[i][j];
}
void setidentity(/*whatever params*/) { std::cout << "yay!"; }
};
对于一般N * M
矩阵,通用模板将被实例化,而仅适用于N * N
matrics,此专业化是更好的匹配。
缺点:代码重复所有常规代码。可以使用基类,但实际上更容易做一些SFINAE魔术(下图)
您还可以通过添加默认为N
和M
至nrows
的隐藏模板参数ncols
和setidentity
以及{{1}来使用SFINAE在条件enable_if
上。
N == M
或者,由于问题标记为C ++ 11,请改用template<typename T, std::size_t nrows, std::size_t ncols>
class Matrix
{
T data[nrows][ncols];
public:
T& operator ()(std::size_t i, std::size_t j)
{
return data[i][j];
}
template <std::size_t N = nrows, std::size_t M = ncols, std::enable_if_t<(N == M)>* = nullptr>
void setidentity(/*whatever params*/) {
static_assert(N == nrows && M == ncols, "invalid");
std::cout << "yay!";
}
};
。
答案 1 :(得分:6)
您可以在以下模式中使用std::enable_if
执行此操作
template <std::size_t r = nrows, std::size_t c = ncols>
typename std::enable_if<r == c>::type setIdentity ()
{ /* do something */ }
一个完整的例子
#include <type_traits>
template<typename T, std::size_t nrows, std::size_t ncols>
class Matrix
{
T data[nrows][ncols];
public:
T& operator ()(std::size_t i, std::size_t j)
{ return data[i][j]; }
template <std::size_t r = nrows, std::size_t c = ncols>
typename std::enable_if<r == c>::type setIdentity ()
{ /* do something */ }
};
int main()
{
Matrix<int, 3, 3> mi3;
Matrix<int, 3, 2> mnoi;
mi3.setIdentity();
// mnoi.setIdentity(); error
return 0;
}
---编辑---
正如Niall的评论(关于TemplateRex的答案,但我的解决方案遭受同样的缺陷)所指出的那样,这个解决方案可以通过这种方式显示行数和列数
mi3.setIdentity<4, 4>();
(但这不是真正的问题(恕我直言),因为mi3
是方阵,setIdentity()
可以使用实际维度(nrows
和ncols
) )甚至是
mnoi.setIdentity<4, 4>()
(这是一个大问题(恕我直言),因为mnoi
不是方阵)。
显然,Niall提出了解决方案(添加static_assert
;类似
template <std::size_t r = nrows, std::size_t c = ncols>
typename std::enable_if<r == c>::type setIdentity ()
{
static_assert(r == nrows && c == ncols, "no square matrix");
/* do something else */
}
或类似的东西)但我建议在std::enable_if
添加相同的支票。
我的意思是
template <std::size_t r = nrows, std::size_t c = ncols>
typename std::enable_if< (r == c)
&& (r == nrows)
&& (c == ncols)>::type setIdentity ()
{ /* do something */ }
答案 2 :(得分:4)
使用伪CRTP为某些内容添加模块化支持。
template<class T, std::size_t nrows, std::size_t ncols>
class Matrix;
template<class T, std::size_t size>
struct MatrixDiagonalSupport {
auto self() { return static_cast<Matrix<T, size, size>*>(this); }
auto self() const { return static_cast<Matrix<T, size, size> const*>(this); }
void setIdentity() {
for (std::size_t i = 0; i < size; ++i) {
for (std::size_t j = 0; j < i; ++j) {
(*self())(i,j) = {};
}
(*self())(i,i) = 1; // hope T supports this!
for (std::size_t j = i+1; j < size; ++j) {
(*self())(i,j) = {};
}
}
}
};
template<class T>
struct empty_t {};
template<bool b, class T>
using maybe= std::conditional_t<b, T, empty_t<T>>;
template<typename T, std::size_t nrows, std::size_t ncols>
class Matrix: public maybe<nrows==ncols,MatrixDiagonalSupport<T, nrows>>
{
// ...
如果我们不是对角线,那么我们从没有继承,如果我们是对角线,我们继承一个实现集合身份的类。
Matrix
的用户如果是正确的话,会从其父级获得.setIdentity()
。
static_cast
内的{p> self()
最终成为零成本抽象,并赋予基类访问子类的权限。
这是伪CRTP,因为我们实际上并没有将派生类类型传递给父级,只是父级有足够的信息来重构它。
该解决方案使该方法成为一种实际方法,并避免任何类型的SFINAE欺骗。
在C ++ 11中,将conditional_t<?>
替换为typename conditional<?>::type
:
template<bool b, class T>
using maybe=typename std::conditional<b, T, empty_t<T>>::type;
一切都应该编译。
答案 3 :(得分:2)
任何其他答案都没有提到的基本但简单的解决方案:您可以使用std::conditional
和继承
它遵循一个最小的工作示例:
#include<type_traits>
#include<cstddef>
struct HasSetIdentity {
void setIdentity() { }
};
struct HasNotSetIdentity {};
template<typename T, std::size_t nrows, std::size_t ncols>
class Matrix: public std::conditional<(nrows==ncols), HasSetIdentity, HasNotSetIdentity>::type
{
T data[nrows][ncols];
public:
T& operator ()(std::size_t i, std::size_t j)
{
return data[i][j];
}
};
int main() {
Matrix<int, 2,2> m1;
m1.setIdentity();
Matrix<int, 2,3> m2;
// Method not available
// m2.setIdentity();
}
如果需要所有子对象共享数据,您仍然可以在层次结构中向下移动数据 这主要取决于真正的问题。
答案 4 :(得分:0)
skypjack和max66都提供了问题的简单答案。这只是使用简单继承的另一种方法,尽管它意味着使用方形矩阵的子类:
template<typename T, std::size_t nrows, std::size_t ncols>
class Matrix
{
protected:
T data[nrows][ncols];
public:
T& operator ()(std::size_t i, std::size_t j)
{
return data[i][j];
}
};
template<typename T, std::size_t N>
class SqMatrix : public Matrix <T, N, N>
{
public:
setIdentity()
{
//Do whatever
}
}