如何有条件地将一个函数添加到类模板?

时间:2016-08-25 20:14:09

标签: c++ class templates c++11 template-specialization

我有一个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成语,但这将定义所有情况的函数。不是吗?

5 个答案:

答案 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!"; }
};

Ddoc Lab

对于一般N * M矩阵,通用模板将被实例化,而仅适用于N * N matrics,此专业化是更好的匹配。

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

稍微更难但更经济的方式

您还可以通过添加默认为NMnrows的隐藏模板参数ncolssetidentity以及{{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!"; } };

Live Example

答案 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()可以使用实际维度(nrowsncols) )甚至是

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欺骗。

Live example

在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)

skypjackmax66都提供了问题的简单答案。这只是使用简单继承的另一种方法,尽管它意味着使用方形矩阵的子类:

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