如何有条件地将函数添加到 class 模板?
How to conditionally add a function to a class template?
我有一个 Matrix class 模板如下:
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
时实例化。当nrows==ncols
为false
时,.setIdentity()
的没有定义。
我正在尝试使用 enable_if
习语,但这将为所有情况定义函数。不是吗?
懒惰和不必要的重复方式
只需添加部分专业化:
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
矩阵,这种特殊化是更好的匹配。
缺点:所有正则代码的代码重复。可以使用 base class,但实际上更容易做一些 SFINAE 魔术(下)
稍难但更经济的方式
您还可以通过将默认为 nrows
和 ncols
的隐藏模板参数 N
和 M
添加到 setidentity
,以及 enable_if
条件 N == M
.
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!";
}
};
或者,由于问题被标记为 C++11,请改用 typename std::enable_if<(N == M)>::type
。
你可以用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 */ }
使用 pseudo-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>>
{
// ...
在这里,如果我们不是对角线,我们从无继承,如果我们是对角线,则class实现集合标识。
Matrix
的用户从它的 parent 中神奇地得到 .setIdentity()
如果它是正确的。
static_cast
inside self()
最终成为一个 zero-cost 抽象并给予基础 class 访问 child class.
这是 pseudo-CRTP 因为我们实际上并没有将派生的 class 类型传递给 parent,只是 parent 重建它的足够信息。
此解决方案使该方法成为实际方法,并避免任何类型的 SFINAE 欺骗。
在 C++11 中将 conditional_t<?>
替换为 typename conditional<?>::type
:
template<bool b, class T>
using maybe=typename std::conditional<b, T, empty_t<T>>::type;
一切都应该编译。
任何其他答案都没有提到的基本但简单的解决方案:您可以使用 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();
}
如果您需要所有子对象共享数据,您仍然可以将数据向下移动。
这主要取决于实际问题。
and 都给出了问题的简单答案。这只是另一种方法,使用简单的继承,尽管这意味着对方矩阵使用子 class:
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
}
}
我有一个 Matrix class 模板如下:
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
时实例化。当nrows==ncols
为false
时,.setIdentity()
的没有定义。
我正在尝试使用 enable_if
习语,但这将为所有情况定义函数。不是吗?
懒惰和不必要的重复方式
只需添加部分专业化:
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
矩阵,这种特殊化是更好的匹配。
缺点:所有正则代码的代码重复。可以使用 base class,但实际上更容易做一些 SFINAE 魔术(下)
稍难但更经济的方式
您还可以通过将默认为 nrows
和 ncols
的隐藏模板参数 N
和 M
添加到 setidentity
,以及 enable_if
条件 N == M
.
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!";
}
};
或者,由于问题被标记为 C++11,请改用 typename std::enable_if<(N == M)>::type
。
你可以用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 */ }
使用 pseudo-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>>
{
// ...
在这里,如果我们不是对角线,我们从无继承,如果我们是对角线,则class实现集合标识。
Matrix
的用户从它的 parent 中神奇地得到 .setIdentity()
如果它是正确的。
static_cast
inside self()
最终成为一个 zero-cost 抽象并给予基础 class 访问 child class.
这是 pseudo-CRTP 因为我们实际上并没有将派生的 class 类型传递给 parent,只是 parent 重建它的足够信息。
此解决方案使该方法成为实际方法,并避免任何类型的 SFINAE 欺骗。
在 C++11 中将 conditional_t<?>
替换为 typename conditional<?>::type
:
template<bool b, class T>
using maybe=typename std::conditional<b, T, empty_t<T>>::type;
一切都应该编译。
任何其他答案都没有提到的基本但简单的解决方案:您可以使用 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();
}
如果您需要所有子对象共享数据,您仍然可以将数据向下移动。
这主要取决于实际问题。
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
}
}