type_traits question

camelCase

The Case of the Mysterious Camel.
Reaction score
362
How would I make a template class instantiable only with built-in types?
Like, only integers, chars, bools, enums, floats and pointers.

I haven't used type_traits at all because I never really got *how* to use it.

Like, for example, this should fail:
Code:
class NonBuiltIn {};
int main () {
    TemplateClass<NonBuiltIn> this_should_fail; //Compile error
    return 0;
}
 

s3rius

Linux is only free if your time is worthless.
Reaction score
130
Not too versed with template metaprogramming myself, but I know the basics (partially :p)

Code:
//Normal class template definition, we need a dummy parameter here though, we default it to void so it doesn't bother anyone.
template<class T, class Dummy=void>
class Builtin; // The actual class definition is incomplete. If this template overload is chosen, you'll get a "class incomplete" error.

//This is the actual class definition. We change the dummy parameter to enable_if, which only allows this overload
//if the condition returns true. Our condition is is_fundamental, which tests whether the type is void or arithmetic.
template<class T>
class Builtin<T, typename std::enable_if<std::is_fundamental<T>::value >::type> {
};


void someFun(){
     Builtin<std::string> a; //Error
     Builtin<float> b; //Success
}

The technique uses SFINAE (substitution failure is not an error). It'll always pick the most appropriate overload for instantiation. If the template parameter T satisfies is_fundamental<T> then it'll become a suited overload. If not, the only overload left is the one with the dummy parameter. Since this one implements Builtin only incomplete it'll return an error.

There's a list of all std type traits
http://msdn.microsoft.com/en-us/library/ff926129.aspx
 

camelCase

The Case of the Mysterious Camel.
Reaction score
362
Oo..
It worked!

However, if I need some methods in that class..
I find myself doing this:
Code:
//Normal class template definition, we need a dummy parameter here though, we default it to void so it doesn't bother anyone.
template<class T, class Dummy=void>
class Builtin; // The actual class definition is incomplete. If this template overload is chosen, you'll get a "class incomplete" error.

//This is the actual class definition. We change the dummy parameter to enable_if, which only allows this overload
//if the condition returns true. Our condition is is_fundamental, which tests whether the type is void or arithmetic.
template<class T>
class Builtin<T, typename std::enable_if<std::is_fundamental<T>::value >::type> {
    BuiltIn method1 (); //return by value
    void method2 ();
    void method3 ();
};

template<class T>
Builtin<T, typename std::enable_if<std::is_fundamental<T>::value >::type>
Builtin<T, typename std::enable_if<std::is_fundamental<T>::value >::type>::method1 () { return stuff; }

template<class T>
void Builtin<T, typename std::enable_if<std::is_fundamental<T>::value >::type>::method2 () {
    //do stuff
}

template<class T>
void Builtin<T, typename std::enable_if<std::is_fundamental<T>::value >::type>::method3 () {
    //do stuff
}

void someFun(){
     Builtin<std::string> a; //Error
     Builtin<float> b; //Success
}

And it looks pretty messy to me.
Is there a way to shorten it? (Besides using a #define macro =x)
 

s3rius

Linux is only free if your time is worthless.
Reaction score
130
Yes, there is. Took me a while to figure it out.

Code:
template<class T, class Dummy=void>
class Builtin;

template<class T>
struct allow{
	typedef typename std::enable_if<std::is_fundamental<T>::value >::type yes;
};

template<class T>
class Builtin<T, typename allow<T>::yes> {
    Builtin method1 (); //return by value
    void method2 ();
    void method3 ();
};


template<class T>
Builtin<T, typename allow<T>::yes >
Builtin<T, typename allow<T>::yes >::method1 () { return Builtin<T, typename allow<T>::yes>(); }


template<class T>
void Builtin<T, typename allow<T>::yes>::method2 () {
    //do stuff
}

template<class T>
void Builtin<T, typename allow<T>::yes>::method3 () {
    //do stuff
}

void someFun(){
     //Builtin<std::string> a; //Error
     Builtin<float> b; //Success
}

I used the enable_if trick to make a new typedef-in-struct. I think there is something like templated typedefs in C++11 that would make the code shorter, but that's as good as I can do.

If you're using several of these templated classes it might be a good idea to rename the struct to make them resemble type_traits more closely:

Code:
template<class T>
struct enable_if_fundamental{
	typedef typename std::enable_if<std::is_fundamental<T>::value >::type type;
};
...
void Builtin<T, typename enable_if_fundamental<T>::type>::method3 () {

This way you can actually re-use them.
 

camelCase

The Case of the Mysterious Camel.
Reaction score
362
Wow, that's neat.
Thanks for the help.

I've been learning about matrices and decided to code a C++ class for it (got lazy to do the computations in my head).
I figured I'd learn template metaprogramming while I was at it.

Anyway, I've got another problem, it goes something like:
Code:
template<typename T, unsigned Rows, unsigned Columns>
class Matrix {
    T matrix[Rows][Columns];
    public:
    template <typename T, unsigned Rows, unsigned Columns>
    typename std::enable_if<Rows==2 && Columns==2, T>::type
    determinant () const {
        T a = matrix[0][0],
          b = matrix[0][1],
          c = matrix[1][0],
          d = matrix[1][1];
        return a*d - b*c;
    }
    
    typename std::enable_if<Rows==2 && Columns==2, Matrix>::type
    adjugate () const {
        Matrix result(*this);
        std::swap(result[0][0], result[1][1]); //Swap a, d
        result[0][1] *= -1; //-b
        result[1][0] *= -1; //-c
        return result;
    }
                        
    typename std::enable_if<Rows==2 && Columns==2, Matrix>::type
    inverse () const {
        return adjugate() * (1/determinant());
    }
};

Compile fails if I try to instantiate Matrix<float, 3, 3>, Matrix<float, 1, 2> and any Matrix class that isn't Matrix<T, 2U, 2U>.

I can't remember where I saw it from, but I remember seeing something like this:
Code:
template<stuff>
class TemplateClass {
    template<other stuff>
    //some method using both "stuff" and "other stuff" to work
};
And I did use that for some other method specializations (don't know what it's called).

But for the determinant(), adjugate() and inverse() methods, their specializations don't rely on any "other stuff" template parameters.
Uhh..
Not sure if I'm making myself clear =x

Guess I'm just wondering why this will SFINAE without error:
Code:
template<stuff>
class TemplateClass {
    template<other stuff>
    typename std::enable_if<*some failed condition*>::type //enable_if fails, so this isn't enabled.
    method () {}
};

While this won't SFINAE without error:
Code:
template<stuff>
class TemplateClass {
    //Compile error because of failure =/
    typename std::enable_if<*some failed condition*>::type //enable_if fails, so this isn't enabled.
    method () {}
};

And how to get around that error.

Edit:
I figured I'd just post what I have so far in case it helps:
Code:
#ifndef JAHS_MATRIX_H
#define JAHS_MATRIX_H
    #include <utility>
    #include <string.h>
    #include <type_traits>
    namespace jahs {
        namespace math {
            template <typename T, unsigned Rows, unsigned Columns>
            class Matrix {
                public:
                    Matrix ();
                    Matrix (const Matrix &other);
                    explicit Matrix (const T (&arr)[Rows][Columns]);
                    
                    T* operator[] (unsigned row);
                    const T* operator[] (unsigned row) const;
                    Matrix operator+ (const Matrix &other) const;
                    Matrix operator- (const Matrix &other) const;

                    #pragma region Matrix<T, Rows, OtherColumns> operator* (const Matrix<T, Columns, OtherColumns> &other) const;
                    template <unsigned OtherColumns>
                    Matrix<T, Rows, OtherColumns> operator* (const Matrix<T, Columns, OtherColumns> &other) const {
                        Matrix<T, Rows, OtherColumns> ret_val;
                        for (unsigned row=0; row<Rows; row++) {
                            for (unsigned col=0; col<OtherColumns; col++) {
                                T sum = static_cast<T>(0);
                                for (unsigned n=0; n<Columns; n++) {
                                    sum += matrix[row][n] * other[n][col];
                                }
                                ret_val[row][col] = sum;
                            }
                        }
                        return ret_val;
                    }
                    #pragma endregion
                    #pragma region Matrix operator* (U scalar) const;
                    template <typename U>
                    typename std::enable_if<std::is_scalar<U>::value, Matrix>::type
                    operator* (U scalar) const {
                        Matrix result;
                        for (unsigned row=0; row<Rows; row++) {
                            for (unsigned col=0; col<Columns; col++) {
                                result[row][col] = matrix[row][col]*scalar;
                            }
                        }
                        return result;
                    }
                    #pragma endregion
                    #pragma region static Matrix identity ();
                    
                    static typename std::enable_if<Rows==Columns, Matrix>::type
                    identity () {
                        static Matrix result;
                        if (result[0][0] != static_cast<T>(1)) {
                            for (unsigned i=0; i<Rows; i++) {
                                result[i][i] = static_cast<T>(1);
                            }
                        }
                        return result;
                    }
                    #pragma endregion
                    #pragma region Matrix operations for two-by-two matrices
                        
                        typename std::enable_if<Rows==2 && Columns==2, T>::type
                        determinant () const {
                            T a = matrix[0][0],
                              b = matrix[0][1],
                              c = matrix[1][0],
                              d = matrix[1][1];
                            return a*d - b*c;
                        }

                        typename std::enable_if<Rows==2 && Columns==2, Matrix>::type
                        adjugate () const {
                            Matrix result(*this);
                            std::swap(result[0][0], result[1][1]); //Swap a, d
                            result[0][1] *= -1; //-b
                            result[1][0] *= -1; //-c
                            return result;
                        }
                        
                        typename std::enable_if<Rows==2 && Columns==2, Matrix>::type
                        inverse () const {
                            return adjugate() * (1/determinant());
                        }
                    #pragma endregion
                private:
                    T matrix[Rows][Columns];
            };

            template <typename T, unsigned Rows, unsigned Columns>
            Matrix<T, Rows, Columns>::Matrix () {}

            template <typename T, unsigned Rows, unsigned Columns>
            Matrix<T, Rows, Columns>::Matrix (const Matrix &other) {
                std::memcpy(&matrix[0], &other.matrix[0], sizeof(T) * Rows * Columns);
            }

            template <typename T, unsigned Rows, unsigned Columns>
            Matrix<T, Rows, Columns>::Matrix (const T (&arr)[Rows][Columns]) {
                std::memcpy(&matrix[0], &arr[0], sizeof(T) * Rows * Columns);
            }

            template <typename T, unsigned Rows, unsigned Columns>
            T* Matrix<T, Rows, Columns>::operator[] (unsigned row) {
                return matrix[row];
            }

            template <typename T, unsigned Rows, unsigned Columns>
            const T* Matrix<T, Rows, Columns>::operator[] (unsigned row) const {
                return matrix[row];
            }

            template <typename T, unsigned Rows, unsigned Columns>
            Matrix<T, Rows, Columns> Matrix<T, Rows, Columns>::operator+ (const Matrix &other) const {
                Matrix ret_val;
                for (unsigned row=0; row<Rows; row++) {
                    for (unsigned col=0; col<Columns; col++) {
                        ret_val[row][col] = matrix[row][col] + other[row][col];
                    }
                }
                return ret_val;
            }

            template <typename T, unsigned Rows, unsigned Columns>
            Matrix<T, Rows, Columns> Matrix<T, Rows, Columns>::operator- (const Matrix &other) const {
                Matrix ret_val;
                for (unsigned row=0; row<Rows; row++) {
                    for (unsigned col=0; col<Columns; col++) {
                        ret_val[row][col] = matrix[row][col] - other[row][col];
                    }
                }
                return ret_val;
            }
        }
    }
#endif
The problem is in the regions "static Matrix identity()" and "Matrix operations for two-by-two matrices".
All the methods there won't compile with this:
The errors all come from Rows and Columns not being "2"
Code:
#include "Matrix.h"
#include <iostream>
using namespace std;
using namespace jahs::math;

int main () {
    float arr[3][3] = {
        {1, 0, 1},
        {0, 2, 1},
        {1, 1, 1}
    };
    float arr2[3][3] = {
        {-1,-1, 2},
        {-1, 0, 1},
        { 2, 1,-2}
    };
    Matrix<float, 2, 3> t;
    Matrix<float, 3, 3> m(arr);
    Matrix<float, 3, 3> m2(arr2);
    Matrix<float, 3, 3> identity = m2*m;
    Matrix<float, 3, 3> m3 = m*identity;
    Matrix<float, 3, 3> m4 = m2*identity*2.f;
    Matrix<float, 4, 4> m5 = Matrix<float, 4, 4>::identity();

    float a_2D_1[2][2] = {
        {1, 2},
        {3, 4}
    };
    Matrix<float, 2, 2> m_2D_1(a_2D_1);
    Matrix<float, 2, 2> m_2D_2 = m_2D_1.inverse();
    Matrix<float, 2, 2> m_2D_3 = m_2D_1 * m_2D_2;

    for (unsigned r=0; r<2; r++) {
        for (unsigned c=0; c<2; c++) {
            cout<< m_2D_3[r][c] << " ";
        }
        cout<< endl;
    }
    
    return 0;
}
 

s3rius

Linux is only free if your time is worthless.
Reaction score
130
That's because templates are a bitch ^^
Template name lookup is a two-phase stage.

I think that all of what I wrote here is sound, however I can't guarantee.

I've killed most of the matrix:

Code:
template <typename T, unsigned Rows, unsigned Columns>
class Matrix {
	public:

		typename std::enable_if<Rows==Columns, Matrix>::type
		identity () {
                        
		}

};

Let's take a look at this, since it's one of the problematic functions. Even though Matrix (the one used in the enable_if) is a dependent type, for the deduction of identity() it's treated as a non-dependent type, because nothing in identity() specifies that Matrix participates in name deduction.
Which means that identity() can and will be resolved at the point of definition:

Code:
void main(){
    Matrix<float, 3,3> m; //Here, code for [B]identity()[/B] must already be deductable for the compiler.
}

What happens if you now define Matrix<float, 3,4> (which is supposed to not generate identity()?
Following code will be generated:

Code:
typename std::enable_if<3==4, Matrix>::type
identity () {
}

What does enable_if<..> evaluate to?
Let's look at how enable_if should be implemented:
Code:
template<bool B, class T = void>
struct enable_if {};
 
template<class T>
struct enable_if<true, T> { typedef T type; };
(from http://en.cppreference.com/w/cpp/types/enable_if)

So, enable_if is a struct which holds a typedef for T, called type. But only if the condition evaluated to true. If false, it'll not hold anything. So in our case there is no typedef type.

Code:
typename std::enable_if<3==4, Matrix>::type //<- for 3==4, type is non-existent
identity () {
}

So that's a simple syntax error right here. Why doesn't SFINAE simply remove the invalid template deduction? It should. However, since there is no dependent name used for identity(), SFINAE won't come into action. So, we need to tell compiler that Matrix is has to be treated as a dependent name.

Code:
template <typename U>
typename std::enable_if<3==4, U>::type
identity () {
}
By adding a template parameter U we can alleviate the problem. However, calling identity isn't as nice anymore, because we need to specify U:
Code:
void main(){
    Matrix<float, 3,3> m;
    m.identity< Matrix<float,3,3> >();
}

And from here on I'm a bit lost too.
In C++11 we can specify default values for class template function parameters:
Code:
template <typename U = Matrix> //Voila
typename std::enable_if<3==4, U>::type
identity () {
}
Which would get rid of all our problems.
VS2010, however (which I think you're using too, iirc), doesn't follow the C++11 standard yet. That stuff is only coming somewhen after release of VS2011.

Soo.... other solutions.. only one comes to mind:
We need to specify U somehow. We can either explicitely name it (like in the example above) or let argument deduction take over:
Code:
template <typename U>
typename std::enable_if<3==4, U>::type
identity (U& u) {
}
....
Matrix<float,3,3> m;
auto id = m.identity(m); //At least no more <>

And at that point it might seem more useful to make identity() a seperate helper function instead of a class member function.

I'm pretty sure there must be some semi-magic template hackery to get the job done, I just don't know of them. I'm not very well versed in templates and everything that goes along with it.

I've looked around on some sites, but I couldn't find much about cases that resemble this specific case (since we don't want member function specialization, but rather prevention).
 

camelCase

The Case of the Mysterious Camel.
Reaction score
362
Wooo weeeeeee, as always, you give great explanations, s3rius!
And that is a nice site >.>

"In C++11 we can specify default values for class template function parameters:"
Yeah, I tried that and it failed =/
I didn't know it was part of the C++11 standard (I was just aimlessly shifting code, praying for a miracle).

And, yeap, I'm using VS2010.

I've been trying to find a solution on my own and the closest I got (to me, at least) was something like:
Code:
class DefaultMatrixType {}; //Every other matrix
class R2C2 : public DefaultMatrixType {}; //2.2 matrix
class R3C3 : public DefaultMatrixType {}; //3.3 matrix

template <unsigned Rows, unsigned Cols, class Dummy=DefaultMatrixType>
class Operations {
    public:
        /*Constructor that takes reference to 2D array*/
    protected:
        /*const reference to 2D array*/
};

template <unsigned Rows, unsigned Cols, class Dummy>
class Matrix {
    public:
        /*All constructors initialize "operations" by passing its internal 2D array*/
        const Operations<Rows, Cols> operations;
};

template <>
class Operations<2, 2, R2C2> : public Operations<2, 2> {
    public:
        /*inverse, adjugate, identity, isSingular, determinant*/
        /*All calculated using the 2D array reference in its base class*/
        Matrix<2, 2> inverse () {
            Matrix<2, 2> result;
            //stuff..
            return result;
        }
}

So, I was thinking I could do this:
Matrix<2, 2> A(some_2D_array);
A = A.operations.identity();

However, this doesn't work because the explicit specialization for class Operations was defined *after* the definition of class Matrix =/
So, class Matrix uses the first definition of class Operations (that has only a constructor and no methods).

=(
 
General chit-chat
Help Users
  • No one is chatting at the moment.
  • Varine Varine:
    How can you tell the difference between real traffic and indexing or AI generation bots?
  • The Helper The Helper:
    The bots will show up as users online in the forum software but they do not show up in my stats tracking. I am sure there are bots in the stats but the way alot of the bots treat the site do not show up on the stats
  • Varine Varine:
    I want to build a filtration system for my 3d printer, and that shit is so much more complicated than I thought it would be
  • Varine Varine:
    Apparently ABS emits styrene particulates which can be like .2 micrometers, which idk if the VOC detectors I have can even catch that
  • Varine Varine:
    Anyway I need to get some of those sensors and two air pressure sensors installed before an after the filters, which I need to figure out how to calculate the necessary pressure for and I have yet to find anything that tells me how to actually do that, just the cfm ratings
  • Varine Varine:
    And then I have to set up an arduino board to read those sensors, which I also don't know very much about but I have a whole bunch of crash course things for that
  • Varine Varine:
    These sensors are also a lot more than I thought they would be. Like 5 to 10 each, idk why but I assumed they would be like 2 dollars
  • Varine Varine:
    Another issue I'm learning is that a lot of the air quality sensors don't work at very high ambient temperatures. I'm planning on heating this enclosure to like 60C or so, and that's the upper limit of their functionality
  • Varine Varine:
    Although I don't know if I need to actually actively heat it or just let the plate and hotend bring the ambient temp to whatever it will, but even then I need to figure out an exfiltration for hot air. I think I kind of know what to do but it's still fucking confusing
  • The Helper The Helper:
    Maybe you could find some of that information from AC tech - like how they detect freon and such
  • Varine Varine:
    That's mostly what I've been looking at
  • Varine Varine:
    I don't think I'm dealing with quite the same pressures though, at the very least its a significantly smaller system. For the time being I'm just going to put together a quick scrubby box though and hope it works good enough to not make my house toxic
  • Varine Varine:
    I mean I don't use this enough to pose any significant danger I don't think, but I would still rather not be throwing styrene all over the air
  • The Helper The Helper:
    New dessert added to recipes Southern Pecan Praline Cake https://www.thehelper.net/threads/recipe-southern-pecan-praline-cake.193555/
  • The Helper The Helper:
    Another bot invasion 493 members online most of them bots that do not show up on stats
  • Varine Varine:
    I'm looking at a solid 378 guests, but 3 members. Of which two are me and VSNES. The third is unlisted, which makes me think its a ghost.
    +1
  • The Helper The Helper:
    Some members choose invisibility mode
    +1
  • The Helper The Helper:
    I bitch about Xenforo sometimes but it really is full featured you just have to really know what you are doing to get the most out of it.
  • The Helper The Helper:
    It is just not easy to fix styles and customize but it definitely can be done
  • The Helper The Helper:
    I do know this - xenforo dropped the ball by not keeping the vbulletin reputation comments as a feature. The loss of the Reputation comments data when we switched to Xenforo really was the death knell for the site when it came to all the users that left. I know I missed it so much and I got way less interested in the site when that feature was gone and I run the site.
  • Blackveiled Blackveiled:
    People love rep, lol
    +1
  • The Helper The Helper:
    The recipe today is Sloppy Joe Casserole - one of my faves LOL https://www.thehelper.net/threads/sloppy-joe-casserole-with-manwich.193585/
  • The Helper The Helper:
    Decided to put up a healthier type recipe to mix it up - Honey Garlic Shrimp Stir-Fry https://www.thehelper.net/threads/recipe-honey-garlic-shrimp-stir-fry.193595/

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top