簡體   English   中英

部分專業化和 SFINAE

[英]Partial specialization and SFINAE

假設我有以下 Matrix 模板類,並且需要將向量表示為 1 x RowSize 或 ColSize x 1 矩陣(以便我可以重用許多與向量兼容的矩陣運算符:乘以 2 個矩陣,將矩陣乘以標量等等):

template <class T, size_t ColumnSize, size_t RowSize>
struct Matrix {
    T [ColumnSize][RowSize];
}

我有兩個問題:

1) 如果我沒有記錯的話,我可以通過部分專業化或在 Matrix 方法上使用 SFINAE 來實現這一點(例如,當 ColSize 或 RowSize 為 1 時啟用“長度”方法)。 上述選項的優缺點是什么?

2)如果我選擇使用部分專業化,有沒有辦法為行向量和列向量定義一個專業化,而不是這樣:

template <class T, size_t ColumnSize>
struct Matrix<T, ColumnSize, 1> {
    T length() const;

    T [ColumnSize][RowSize];
}

template <class T, size_t RowSize>
struct Matrix<T, 1, RowSize> {
    T length() const;

    T [ColumnSize][RowSize];
}

這真的取決於要求是“通用 Matrix 不能有長度方法”(然后應該使用 SFINAE 或繼承),還是“不能在通用 Matrix 上調用length ”(然后是length體內部的 static_assert適用)。 第三種選擇是不做任何事情並使length適用於通用矩陣,但仍有其他操作僅適用於向量。

對於“一般矩陣不得有長度方法”。 為了節省空間,我將使用int和較短的符號名稱。 您應該使用std::integral_constant而不是int_ 如果參數是非類型參數,則需要int_包裝器,因為語言限制禁止專門用於更復雜的計算。 因此,我們將參數設為一個類型,並將值包裝到其中。 以下不使用SFINAE,而是繼承。 使用向量混合基類的d() ,您可以隨時從混合類內部訪問向量的數據。

template<int> struct int_;

template<typename D, typename S>
struct V { };

template<typename T, int A, int B>
struct M : V<M<T, A, B>, int_<A * B>> {
   T data[A][B];
};

template<typename T, int A, int B>
struct V<M<T, A, B>, int_<A + B - 1>> { 
   int length() const { return A * B; }

   M<T, A, B> *d() { return static_cast<M<T, A, B>*>(this); }
   const M<T, A, B> *d() const { return static_cast<const M<T, A, B>*>(this); }
};

這是現在

int main() { 
   M<float, 1, 3> m1; m1.length();
   M<float, 3, 1> m2; m2.length();
   // M<float, 3, 2> m3; m3.length(); error
}

對於“不能在一般矩陣上調用length ”,您可以使用“static_assert”

template<typename T, int A, int B>
struct M {
   int length() const {
      static_assert(A == 1 || B == 1, "must not be called on a matrix!");
      return A * B;
   }

   T data[A][B];
};

選擇最合適的

SFINAE 只能根據自己的參數禁用模板聲明。 使用封閉類的參數禁用非模板成員函數(例如length有點不自然。 該技術如下所示:

template <class T, size_t RowSize, size_t ColumnSize>
struct Matrix {
    // SFINAE turns a non-template into a template.
    // Introduce a fake dependency so enable_if resolves upon function call.
    template< typename size_t_ = size_t >
    static constexpr
    // Now write the actual condition within the return type.
    std::enable_if_t< RowSize == 1 || ColumnSize == 1
    , size_t_ > length() const;
        { return RowSize * ColumnSize; }

    T [ColumnSize][RowSize];
}

如果你能忍受這種丑陋,那么你就會得到你想要的:一個所需類型的函數,當條件不滿足時,它完全消失。 不需要其他支持。

另一方面,部分特化會影響整個類定義。 由於在每個部分特化中復制整個類通常是糟糕的設計,因此按照 Johannes 的描述使用繼承。

只是為他的答案添加一個替代方案,可以在部分專業化中使用 SFINAE,以避免巧妙的代數和int_問題。

// Add "typename = void" for idiomatic class SFINAE.
template<size_t RowSize, size_t ColumnSize, typename = void>
struct maybe_vector_interface { }; // Trivial specialization for non-vectors

// Partial specialization for vectors:
template<size_t RowSize, size_t ColumnSize>
struct maybe_vector_interface< RowSize, ColumnSize,
    std::enable_if_t< RowSize == 1 || ColumnSize == 1 > > { 
    static constexpr int length() const
        { return RowSize * ColumnSize; }
};

template<typename T, size_t RowSize, size_t ColumnSize>
struct Matrix
    : maybe_vector_interface<RowSize, ColumnSize> {
    T data[RowSize][ColumnSize];
};

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM