如何有条件地将函数添加到类模板中

How to conditionally add a function to a class template?

本文关键字:添加 函数 有条件      更新时间:2023-10-16

我有一个矩阵类模板,如下所示:

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==ncolstrue时的实例化。并且当nrows==ncolsfalse时,将有没有.setIdentity()的定义。

我正在尝试使用enable_if习惯用法,但这将定义所有情况下的函数。不是吗?

您可以在以下模式中使用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>();

(但这不是一个真正的问题(IMHO(,因为mi3是一个正方形矩阵,setIdentity()可以与真实维度(nrowsncols(一起工作(,甚至与一起工作

mnoi.setIdentity<4, 4>()

(这是一个大问题(IMHO(,因为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 */ 
     }

或者类似的东西(,但是我建议在CCD_ 15中添加相同的检查。

我是说

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 */ }

懒惰和不必要的重复方式

只需添加一个部分专业化:

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矩阵,这种专门化是更好的匹配。

缺点:所有正则代码的代码重复。可以使用基类,但实际上做一些SFINAE魔术更容易(如下(

一种稍微困难但更经济的方式

您还可以通过添加隐藏模板参数NM来使用SFINAE,这些参数默认为nrowsncolssetidentity,并在条件为N == M时添加到enable_if

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

实时示例

使用伪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()

CCD_ 29内部的CCD_。

这是伪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;

一切都应该编译。

一个基本但简单的解决方案,其他答案都没有提到:您可以使用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();
}

如果需要所有子对象共享数据,仍然可以在层次中向下移动数据
这主要取决于真正的问题。

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
    }
}