7

how would you go about using non-type template parameter comparison in a std::enable_if? I can not figure out how to do this again. (I once had this working, but I lost the code so I can't look back on it, and I can't find the post I found the answer in either.)

Thank you in advance for any help on this topic.

template<int Width, int Height, typename T>
class Matrix{
    static
    typename std::enable_if<Width == Height, Matrix<Width, Height, T>>::type
    Identity(){
        Matrix ret;
        for (int y = 0; y < Width; y++){
            elements[y][y] = T(1);
        }
        return ret;
    }
}

Edit: Fixed missing bracket as pointed out in comments.

Czipperz
  • 3,268
  • 2
  • 18
  • 25
LostOfThought
  • 113
  • 1
  • 6
  • 3
    I'd probably use a `static_assert` for that. It provides clear error messages. – chris May 18 '13 at 01:43
  • `static_assert` is indeed the right tool: `std::enable_if` is for SFINAE, and there's no SFINAE possible for a non-template member of a class template. – Luc Danton May 18 '13 at 01:53
  • I have thought of using `static_assert`, ultimately I may end up using it. But I did have this working before, and with the advantage of auto complete not even listing the function for non-square matrices in the first place. Largely at this point though, is trying to figure out how I once had this done with strictly `std::enable_if`. – LostOfThought May 18 '13 at 02:05
  • There is a `>` missing at the end of the `enable_if` line. Actually (after this correction) your code works, however it is is not different that a `static_assert` (it gives a compilation error when calling `Identity` for non-square matrix type.) It is just more obscure than a `static_assert`. – alfC May 18 '13 at 02:49
  • @alfC, even after this correction, the OP's code doesn't work, since `Identity` function should be template. Just try to instantiate `Matrix<1, 2, int>`. And this is differs from `static_assert` behavior. – awesoon May 18 '13 at 02:52
  • @alfC: I got bitten just like you. Even after that correction, a non-square matrix can't be instantiated at all. – syam May 18 '13 at 02:52
  • @syam, you are right! I am posting an answer that may be what the OP is looking for. – alfC May 18 '13 at 02:53

2 Answers2

6

It all depends on what kind of error/failure you want to raise on invalid code. Here it is one possibility (leaving aside the obvious static_assert(Width==Height, "not square matrix");)

(C++98 style)

#include<type_traits>
template<int Width, int Height, typename T>
class Matrix{
public:
    template<int WDummy = Width, int HDummy = Height>
    static typename std::enable_if<WDummy == HDummy, Matrix>::type
    Identity(){
        Matrix ret;
        for (int y = 0; y < Width; y++){
        // elements[y][y] = T(1);
        }
        return ret;
    }
};

int main(){
    Matrix<5,5,double> m55;
    Matrix<4,5,double> m45; // ok
    Matrix<5,5, double> id55 = Matrix<5,5, double>::Identity(); // ok
//  Matrix<4,5, double> id45 = Matrix<4,5, double>::Identity(); // compilation error! 
//     and nice error: "no matching function for call to ‘Matrix<4, 5, double>::Identity()"
}

EDIT: In C++11 the code can be more compact and clear, (it works in clang 3.2 but not in gcc 4.7.1, so I am not sure how standard it is):

(C++11 style)

template<int Width, int Height, typename T>
class Matrix{
public:
    template<typename = typename std::enable_if<Width == Height>::type>
    static Matrix
    Identity(){
        Matrix ret;
        for(int y = 0; y < Width; y++){
            // ret.elements[y][y] = T(1);
        }
        return ret;
    }
};

EDIT 2020: (C++14)

template<int Width, int Height, typename T>
class Matrix{
public:
    template<typename = std::enable_if_t<Width == Height>>
    static Matrix
    Identity()
    {
        Matrix ret;
        for(int y = 0; y < Width; y++){
        //  ret.elements[y][y] = T(1);
        }
        return ret;
    }
};

(C++20) https://godbolt.org/z/cs1MWj

template<int Width, int Height, typename T>
class Matrix{
public:
    static Matrix
    Identity()
        requires(Width == Height)
    {
        Matrix ret;
        for(int y = 0; y < Width; y++){
        //  ret.elements[y][y] = T(1);
        }
        return ret;
    }
};
alfC
  • 14,261
  • 4
  • 67
  • 118
  • Just beat me to finishing up my own answer on the question. Thanks for the answer regardless. Marked as accepted. – LostOfThought May 18 '13 at 03:03
  • 1
    In c++11 there is a more compact alternative: ` template::type> static Matrix Identity(){...} ` – alfC May 18 '13 at 03:45
  • Is `WDummy` even needed? MSVC: CTP Nov '12 with /W4 compiles just fine without it. – LostOfThought May 18 '13 at 04:47
  • @LostOfThought, do you mean in the original answer? You can get away with `template static typename std::enable_if >::type Identity(){...}`. In the comment I still need to have `WDummy`, but I can do get rid of `Dummy`: `template::type> static Matrix Identity(){...}` – alfC May 18 '13 at 06:01
  • 1
    I thought that in C++98, function templates could not have default parameters. – Puppy May 18 '13 at 07:45
  • I think the second option only works in C++11. The first option can be adapted to work in C++98 (e.g. by using `boost::enable_if_c`) – alfC May 18 '13 at 07:51
1

I found the answer to my question here: Using C++11 std::enable_if to enable...

In my solution, SFINAE occurs within my templated return type, therefore making the function template in itself valid. In the course of this, the function itself also becomes templated.

template<int Width, int Height, typename T>
class Matrix{
    template<typename EnabledType = T>
        static
        typename Matrix<Width, Height,
            typename std::enable_if<Width == Height, EnabledType>::type>
        Identity(){
        Matrix ret;
        for (int y = 0; y < Width; y++){
            ret.elements[y][y] = T(1);
        }
        return ret;
    }
}
Community
  • 1
  • 1
LostOfThought
  • 113
  • 1
  • 6
  • I added a more compact version in my answer, but it doesn't work in `gcc`. BTW, the `typename` before `Matrix` is at best redundant. – alfC May 18 '13 at 06:23
  • The code in your Edit is the same as in my new answer, there is no point in repeating it here. – alfC May 18 '13 at 07:36
  • I see that now. Wasn't intended. I've been messing with this code all night, I really am that bored. Regardless, thank you for your help. – LostOfThought May 18 '13 at 07:40
  • Anyways, are you really implementing a matrix class?, not to disanimate you but did you take a look at uBlas and Eigen http://eigen.tuxfamily.org/index.php?title=Main_Page (this one even has static array sizes like the one you are designing), but is cool to reimplement, I am sure it doesn't have a cool `Identity` static function. – alfC May 18 '13 at 07:53
  • Well, a few buddies of mine had this idea of making a game, one is currently in college, mainly taking asm classes right now, and I came from AS3 where there's a lot of hand-holding. Both of us seem to be doing things the hardest way possible. I'm writing everything from scratch, even my own OpenGL loaders, and it's been a uphill struggle for me this entire time, especially reading the 'proper' way to do things in C++11, and having things robust enough for any future things I want to make. (Let alone everything being cross-platform as well, going only off of the system APIs andOpenGL)tl;dr:Yes – LostOfThought May 18 '13 at 08:12
  • On the plus side, all I really have left now is strictly the Matrix/Matrix math functions, most else is already done including projections, the like. (Yes, backwards, I know.) – LostOfThought May 18 '13 at 08:16