Combining templates with..err.. macros and overloading?

This is a discussion on Combining templates with..err.. macros and overloading? within the C++ Programming forums, part of the General Programming Boards category; I thought I could do this with partial specialization but was soon in deep water. I want my template function ...

  1. #1
    Registered User manasij7479's Avatar
    Join Date
    Feb 2011
    Location
    Kolkata@India
    Posts
    2,490

    Combining templates with..err.. macros and overloading?

    I thought I could do this with partial specialization but was soon in deep water.
    I want my template function to call different functions depending on the three or four template arguments (A type, an(or two) int, an enum).

    But the problem is that for each combination of the arguments, the function to be called has a different name (here comes macros ) and different sorts of arguments (and here, overloading ).

    I just can't think of a way to do this.
    Are macros the tricky way out for doing this ?

    What I think, my function should appear as:
    Code:
    template<typename T, Type S,int C1,int C2=0> Program::setUniform( /*No idea, other than writing out all the overloads*/);
    
    //where T is float, int (or double for OpenGL >= 4.0)
    //S is an enum which can denote ordinary, vector and matrix.
    The whole list of functions the single template would have to cover is:
    Code:
    void glUniform1f(GLint  location,  GLfloat  v0);
    void glUniform2f(GLint  location,  GLfloat  v0,  GLfloat  v1);
    void glUniform3f(GLint  location,  GLfloat  v0,  GLfloat  v1,  GLfloat  v2);
    void glUniform4f(GLint  location,  GLfloat  v0,  GLfloat  v1,  GLfloat  v2,  GLfloat  v3);
    
    void glUniform1i(GLint  location,  GLint  v0);
    void glUniform2i(GLint  location,  GLint  v0,  GLint  v1);
    void glUniform3i(GLint  location,  GLint  v0,  GLint  v1,  GLint  v2);
    void glUniform4i(GLint  location,  GLint  v0,  GLint  v1,  GLint  v2,  GLint  v3);
    
    void glUniform1fv(GLint  location,  GLsizei  count,  const GLfloat * value);
    void glUniform2fv(GLint  location,  GLsizei  count,  const GLfloat * value);
    void glUniform3fv(GLint  location,  GLsizei  count,  const GLfloat * value);
    void glUniform4fv(GLint  location,  GLsizei  count,  const GLfloat * value);
    
    void glUniform1iv(GLint  location,  GLsizei  count,  const GLint * value);
    void glUniform2iv(GLint  location,  GLsizei  count,  const GLint * value);
    void glUniform3iv(GLint  location,  GLsizei  count,  const GLint * value);
    void glUniform4iv(GLint  location,  GLsizei  count,  const GLint * value);
    
    void glUniformMatrix2fv(GLint  location,  GLsizei  count,  GLboolean  transpose,  const GLfloat * value);
    void glUniformMatrix3fv(GLint  location,  GLsizei  count,  GLboolean  transpose,  const GLfloat * value);
    void glUniformMatrix4fv(GLint  location,  GLsizei  count,  GLboolean  transpose,  const GLfloat * value);
    void glUniformMatrix2x3fv(GLint  location,  GLsizei  count,  GLboolean  transpose,  const GLfloat * value);
    void glUniformMatrix3x2fv(GLint  location,  GLsizei  count,  GLboolean  transpose,  const GLfloat * value);
    void glUniformMatrix2x4fv(GLint  location,  GLsizei  count,  GLboolean  transpose,  const GLfloat * value);
    void glUniformMatrix4x2fv(GLint  location,  GLsizei  count,  GLboolean  transpose,  const GLfloat * value);
    void glUniformMatrix3x4fv(GLint  location,  GLsizei  count,  GLboolean  transpose,  const GLfloat * value);
    void glUniformMatrix4x3fv(GLint  location,  GLsizei  count,  GLboolean  transpose,  const GLfloat * value);
    Last edited by manasij7479; 07-09-2012 at 11:37 AM.
    Manasij Mukherjee | gcc-4.8.2 @Arch Linux
    Slow and Steady wins the race... if and only if :
    1.None of the other participants are fast and steady.
    2.The fast and unsteady suddenly falls asleep while running !



  2. #2
    Registered User
    Join Date
    Jul 2008
    Posts
    35
    nvm did not read
    Last edited by Florian; 07-09-2012 at 11:50 AM.

  3. #3
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    3,800
    O_o

    You can do this with partial specialization. You can do this with tables. You can do this with macros.

    Have you considered that the functions signatures are nearly identical? That the are polymorphic by nature?

    What is the purpose of an abstraction if it only duplicates complexity? What do you gain?

    Have you considered that communication with shaders is elemental by nature? Have you considered that those elements are rather naturally expressed as associated with primitives like `vector' and `matrix'?

    Soma

  4. #4
    Registered User manasij7479's Avatar
    Join Date
    Feb 2011
    Location
    Kolkata@India
    Posts
    2,490
    Quote Originally Posted by phantomotap View Post
    Have you considered that the functions signatures are nearly identical? That the are polymorphic by nature?
    The word 'polymorphic' along with "associated with primitives like `vector' and `matrix' " gives me some new ideas to try out, thanks.

    What is the purpose of an abstraction if it only duplicates complexity? What do you gain?
    Now I realize that my previous line of thought on this was rather mechanical.
    Have you considered that communication with shaders is elemental by nature?
    Elemental ?
    Manasij Mukherjee | gcc-4.8.2 @Arch Linux
    Slow and Steady wins the race... if and only if :
    1.None of the other participants are fast and steady.
    2.The fast and unsteady suddenly falls asleep while running !



  5. #5
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    3,800
    Elemental ?
    Communication with shaders is indivisible from the content being expressed.

    Code:
    uniform mat4 some_transformation;
    Code:
    class Matrix;
    // ...
    Matrix my_transform;
    // ...
    whatever->setUniform("some_transformation", my_transform);
    Soma

  6. #6
    Registered User
    Join Date
    Apr 2006
    Posts
    2,012
    You can have functions with the same name if they have a different number of arguments without macros or templates. You can also have functions with default arguments. A common strategy is to pass optional parameters as null-able pointers.

    As for the function names that are all glUniformMatrix*x*fv, you can indeed use template arguements for the numbers. It would look like this:
    Code:
    template <int x, int y> void glUniformMatrix(GLint  location,  GLsizei  count,  GLboolean  transpose,  const GLfloat * value);
    Note that function template definitions go in a header file.

    If the number of template parameters is variable, that is also possible to specify, but you need to be able to specify the function internals in terms of variable argument number.

    EDIT: One thing to keep in mind is that if you use templates or multiple functions, then your internal code will compile to separate code for each function. You may want to limit the size of your executable/library. In this case you'll want to use the pointer method (ie pass a pointer for each optional parameter, and pass integer template arguments as function arguments).
    Last edited by King Mir; 07-09-2012 at 04:26 PM.
    It is too clear and so it is hard to see.
    A dunce once searched for fire with a lighted lantern.
    Had he known what fire was,
    He could have cooked his rice much sooner.

  7. #7
    Registered User manasij7479's Avatar
    Join Date
    Feb 2011
    Location
    Kolkata@India
    Posts
    2,490
    This is what my design looks like :
    (this approach would take a lot of manual, duplicated work (which I can't see an way out, in any other way) to implement, but should have a very clean interface)
    Code:
        template<typename T,int D>
        class Vector
        {/*Would implement a generic array*/};
        
        template<> 
        class Vector<float,2>
        {
        public:
            Vector(float x_,float y_):x(x_),y(y_){};
            void sendTo(Program& p,const std::string& name)
            {
                Program::Use use(p.getHandle());
                auto location = glGetUniformLocation(p.getHandle(),name.c_str());
                glUniform2f(location,x,y);
            }
            float x,y;
        };
        typedef Vector<float,2> vec2;
    I don't really see an utility of keeping the vectors generic, but this could be useful later.
    Manasij Mukherjee | gcc-4.8.2 @Arch Linux
    Slow and Steady wins the race... if and only if :
    1.None of the other participants are fast and steady.
    2.The fast and unsteady suddenly falls asleep while running !



  8. #8
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    3,800
    O_o

    You can't see a way that you can reduce the duplication even though my post shows you most of the keywords?

    What if I added "CRTP", "partial functional specialization through traits", and this example?

    *shrug*

    Don't be confused though, I still question the utility of duplicating functionality in something is supposed to be an abstraction.

    I'm just saying that you can eliminate some of the duplication cleanly by isolating the changed functionality and relying on an interface to that functionality at a higher level.

    Soma

    Code:
    template<typename T>
    struct tables
    // ...
    template<typename T,int D>
    struct vector
    // ...
    STATIC_ASSERT(D >= 2 && D <= 4);
    // ...
    void sendTo(Program& p,const std::string& name)
    {
        // ...
        tables<T>::glUniformv[D](location, D, values);
        // ...
    }
    // ...

  9. #9
    Registered User manasij7479's Avatar
    Join Date
    Feb 2011
    Location
    Kolkata@India
    Posts
    2,490
    Unfortunately, this seems to run into problems: (probably because of the way glew handles the declaration of the opengl functions)
    Code:
        template<typename T>
        struct UniformFunctionTable
        {};
        template<>
        struct UniformFunctionTable<float>
        {
            static decltype(glUniform1fv) Vector[5];
            
        };
    
        decltype(glUniform1fv) UniformFunctionTable<float>::Vector[5] =
        {
            nullptr,glUniform1fv,glUniform2fv,glUniform3fv,glUniform4fv
        };
    Whenever I use the table, the entries turn out to be nullptrs, as proven by assert
    Code:
            void sendTo(Program& p,const std::string& name)
            {
                Program::Use use(p.getHandle());
                auto location = glGetUniformLocation(p.getHandle(),name.c_str());
                assert(UniformFunctionTable<float>::Vector[D]==nullptr);// <-- This doesn't fail, ever ! :(
                UniformFunctionTable<T>::Vector[D](location,1,ptr());
                
            }
    In case the problem is somewhere in the code above, I have no idea where it can be.
    Last edited by manasij7479; 07-18-2012 at 12:33 AM.
    Manasij Mukherjee | gcc-4.8.2 @Arch Linux
    Slow and Steady wins the race... if and only if :
    1.None of the other participants are fast and steady.
    2.The fast and unsteady suddenly falls asleep while running !



  10. #10
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    3,800
    O_o

    It has nothing to do with the declarations of functions because they aren't functions within "GLEW".

    They are pointers to functions. Specifically, they are global, probably shared, pointers to functions which get the "initialized to null" treatment with values only set during library initialization.

    Your array treats these pointers as immediate values. That is a problem. (You are essentially initializing your array to null using named variables.) Treat them as references and you'll be fine. (That means declaring the array as being a pointer to the declared type and chasing the pointer at the relevant index.)

    If you are worried about the indirection, make the array constant and most every compiler will give you the exact same performance.

    Soma

  11. #11
    Registered User manasij7479's Avatar
    Join Date
    Feb 2011
    Location
    Kolkata@India
    Posts
    2,490
    Thanks, it worked.
    I don't know if you meant an array of pointers or a a pointer itself.
    I went for the former because I won't have to allocate memory explicitly for that, at the cost of having the code use another layer of indirection.

    The only thing, hopefully, remaining is the constructors.
    Now it takes an std::initializer_list and std::copy 's the arguments into the private std::array .
    But that seems rather ficke. (as does using variable arguments)

    Is there a way, say.. by using something like std::enable_if , so that the constructor 'enabled' depends on a template argument of the class ?

    Also, where does CRTP figure into this? Aren't things like this based on SFINAE (like std::conditional or enable_if )?
    Manasij Mukherjee | gcc-4.8.2 @Arch Linux
    Slow and Steady wins the race... if and only if :
    1.None of the other participants are fast and steady.
    2.The fast and unsteady suddenly falls asleep while running !



  12. #12
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    3,800
    Is there a way, say.. by using something like std::enable_if , so that the constructor 'enabled' depends on a template argument of the class?
    Yes. That's one of a precious few ways to get reasonable heterogeneous parameter selection in template constructors.

    However, it doesn't buy you anything here. The "SFINAE" buys you optional implementation possibilities depending on the availability of other facilities.

    Consider which of the structures in the included example (C++11) gives the most reasonable feedback.

    You see, "SFINAE" works here just fine, but attempting to use a facility that isn't enabled produces ugly error messages.

    "SFINAE" offers a lot of possibilities, but it usually adds the overhead of the dreaded "template error messages" with which some of your clients (even if you are your only client) may not be able to cope. This will probably sound weird, but don't consider "SFINAE" an implementation technique in and of itself; it is, again, a facility that offers the possibility of selecting implementations based on the available facilities.

    Also, be aware that `std::enable_if' brings problems to the table as well.

    Also, where does CRTP figure into this?
    I can think of about eight more ways to do this without manually duplicating all the class code for every "collision" without venturing in to macros.

    You've chosen a way that you could see based on this hints I gave you.

    That just isn't the way I would've done it.

    What you've done as a table I would have done with traits and "CRTP" which implies something like the shorter example below (still borrowing heavily from posted code).

    Code:
    template<typename T>
    struct typebias
    // ...
    template<typename T,int D>
    struct countbias
    // ...
    template<typename B, typename T, int D>
    gluniform_imp
    // ...
    template<typename B>
    gluniform_int: public gluniform_imp<B, B::T, B::D>
    //
    template<typename T,int D>
    struct vector: private gluniform_int<vector<T,D> >
    {
    // ...
    STATIC_ASSERT(D >= 2 && D <= 4);
    // ...
    void sendTo(Program& p,const std::string& name)
    {
        // ...
        glUniform(location, D, values); // a member function
        // ...
    }
    // ...
    };
    // ...
    Soma

    Code:
    #include <iostream>
    #include <type_traits>
    
    template
    <
        typename FType
      , std::size_t FElements
    >
    struct STest1
    {
        template
        <
            typename FUnused = FType
        >
        STest1
        (
            FType fOne
          , typename std::enable_if<(FElements == 1) && std::is_same<FType, FUnused>::value>::type * fUnused = 0
        )
        {
        }
        template
        <
            typename FUnused = FType
        >
        STest1
        (
            FType fOne
          , FType fTwo
          , typename std::enable_if<(FElements == 2) && std::is_same<FType, FUnused>::value>::type * fUnused = 0
        )
        {
        }
    };
    
    template
    <
        typename FType
      , std::size_t FElements
    >
    struct STest2
    {
        STest2
        (
            FType fOne
        )
        {
            static_assert(FElements == 1, "STest constructor called with wrong number of arguments");
        }
        STest2
        (
            FType fOne
          , FType fTwo
        )
        {
            static_assert(FElements == 2, "STest constructor called with wrong number of arguments");
        }
    };
    
    int main()
    {
        STest1<int, 1> s1(1);
        //STest1<int, 1> s2(1, 2);
        STest1<int, 2> s3(1, 2);
        //STest1<int, 2> s4(1);
        STest2<int, 1> s5(1);
        //STest2<int, 1> s6(1, 2);
        STest2<int, 2> s7(1, 2);
        //STest2<int, 2> s8(1);
        return(0);
    }

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Problem with macros and templates
    By Nickedname in forum C++ Programming
    Replies: 14
    Last Post: 10-26-2011, 06:52 AM
  2. problem while overloading == for templates in dev c++
    By shikhardeep in forum C++ Programming
    Replies: 3
    Last Post: 10-16-2011, 04:15 AM
  3. Macros inside of macros
    By Chewie8 in forum C Programming
    Replies: 2
    Last Post: 02-24-2008, 02:51 AM
  4. Templates and Macros plus more...
    By Monkeymagic in forum C++ Programming
    Replies: 8
    Last Post: 01-20-2007, 04:53 PM
  5. overloading macros?
    By *ClownPimp* in forum C++ Programming
    Replies: 9
    Last Post: 01-24-2002, 03:27 PM

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21