How to conditionally add a function to a class template?

前端 未结 5 2016
你的背包
你的背包 2020-12-29 10:45

I have a Matrix class template as follows:

template
class Matrix
{
    T data[nrows][ncols];
public:
         


        
相关标签:
5条回答
  • 2020-12-29 11:29

    Use a pseudo-CRTP to add modular support for something.

    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>>
    {
      // ...
    

    Here we inherit from nothing if we aren't diagonal, and a class implementing set identity if we are diagonal.

    Users of Matrix get .setIdentity() from its parent magically if it is right.

    static_cast inside self() ends up being a zero-cost abstraction and giving the base class access to the child class.

    This is pseudo-CRTP because we don't actually pass the derived class type to the parent, just enough information for the parent to reconstruct it.

    This solution makes the method an actual method, and avoids any kind of SFINAE trickery.

    Live example

    In C++11 replace conditional_t<?> with typename conditional<?>::type:

    template<bool b, class T>
    using maybe=typename std::conditional<b, T, empty_t<T>>::type;
    

    and everything should compile.

    0 讨论(0)
  • 2020-12-29 11:30

    skypjack and max66 have both presented simple answers to the problem. This is just an alternate way of doing it, using simple inheritance, although it means the use of a child class for square matrices:

    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
        }
    }
    
    0 讨论(0)
  • 2020-12-29 11:34

    A basic, but simple solution not mentioned by any other answer: you can use std::conditional and inheritance.
    It follows a minimal, working example:

    #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();
    }
    

    You can still move data down the hierarchy if you need them to be shared by all the subobjects.
    It mostly depends on the real problem.

    0 讨论(0)
  • 2020-12-29 11:44

    The lazy and needlessly repetitive way

    Just add a partial specialization:

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

    Live Example

    For general N * M matrices, the general template will be instantiated, whereas only for N * N matrics, this specialization is a better match.

    Disadvantage: code repetition of all regular code. Could use a base class, but it's actually easier to do some SFINAE magic (below)

    A slightly harder but more economical way

    You can also use SFINAE by adding hidden template parameters N and M that default to nrows and ncols to setidentity, and to enable_if on the condition 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!"; 
        }
    };
    

    Or, since the question was tagged C++11, use typename std::enable_if<(N == M)>::type instead.

    Live Example

    0 讨论(0)
  • 2020-12-29 11:50

    You can do it with std::enable_if in the following mode

    template <std::size_t r = nrows, std::size_t c = ncols>
    typename std::enable_if<r == c>::type setIdentity ()
     { /* do something */ }
    

    A full example

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

    --- EDIT ---

    As pointed in a comment by Niall (regarding the TemplateRex's answer, but my solution suffer from the same defect) this solution can be circonvented expliciting the number of rows and columns in this way

    mi3.setIdentity<4, 4>();
    

    (but this isn't a real problem (IMHO) because mi3 is a square matrix and setIdentity() could work with real dimensions (nrows and ncols)) or even with

    mnoi.setIdentity<4, 4>()
    

    (and this is a big problem (IMHO) because mnoi isn't a square matrix).

    Obviously there is the solution proposed by Niall (add a static_assert; something like

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

    or something similar) but I propose to add the same check in std::enable_if.

    I mean

    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 */ }
    
    0 讨论(0)
提交回复
热议问题