Thread: Best C++ Function Prototype

  1. #1
    Registered User
    Join Date
    Dec 2010
    Posts
    19

    Best C++ Function Prototype

    Hi,

    I'm working on improving my C++ skills. I'm porting over a Python library I wrote to C++. There's a function in the library, and I'm wondering how best to represent the function's arguments in C++. I'm just getting started with templates, and I think they might be part of an ideal solution.

    So, how should the following pseudo-function prototype:
    Code:
    func(  type_a arg_a,  type_b arg_b,  int arg_c,  type_d arg_d,  type_e arg_e,  type_f arg_f,  int arg_g=8  );
    //type_a = NULL, SDL_Surface*, float*, double*, unsigned char*, char*, std::string, std::string*
    //type_b = int, int*
    //type_d = int, bool
    //type_e = int, bool
    //type_f = NULL, int*
    ...be written in C++? Note also that I need to know what type the arguments are in order to process them.

    Thanks,
    Ian
    Last edited by Geometrian; 01-21-2011 at 04:36 PM. Reason: code tags

  2. #2
    and the Hat of Guessing tabstop's Avatar
    Join Date
    Nov 2007
    Posts
    14,336
    bool is implicitly convertible to int (unless you are really really counting on being able to tell the difference) so type_d and type_e can just be int. When you say type_f can be NULL, what do you mean? If you mean a null pointer, then that null pointer still has to have a type (null is the value, not the type), in which case you can just go with int*.

    Almost everything in type_a is a pointer (see above for discussion of NULL), so that std::string is a bit obnoxious. I would probably make this a template on T* (i.e., a template to a pointer) and then do an override (not really a specialization I guess) with std::string there.

    type_b is a little weird, as I'm not sure why sometimes you might want an int and sometimes an int*. The "right" solution would require knowing more about what's going on, and what the difference is between the two.

  3. #3
    Registered User
    Join Date
    Dec 2010
    Posts
    19
    Hi,

    You're right, I probably should explain some of the context of the problem. The function is the constructor for a two-dimensional texture in an OpenGL utility library.

    There are a whole lot of arbitrarily #define-d integer constants that are used.
    Code:
    func(  type_a arg_a,  type_b arg_b,  int arg_c,  type_d arg_d,  type_e arg_e,  type_f arg_f,  int arg_g=8  );
    
    //type_a = NULL, SDL_Surface*, float*, double*, unsigned char*, char*, std::string, std::string*
    //type_a is the data field.  One ought to be able to pass NULL to it, (or completely omit it
    //for the same result).  SDL_Surface* points to a data surface, while float*, double*, and
    //unsigned char* point to actual data.  char*, std::string, and std::string* are
    //filenames where data can be loaded from.  
    
    //type_b = int, int*
    //This is the rect argument, which specifies how the rectangular data is to be
    //interpreted.  It can either be a flag (int) or a pointer to a 4-element array defining
    //a rectangle
    
    //type_c = int
    //A format flag, specifying how OpenGL should use the data
    
    //type_d = int, bool
    //This is the minification filter argument.  It can either be a flag (int) or a bool.
    //Because the flags are arbitrarily defined, I'm hesitant to just cast the bool to an int.
    //Although I could probably ensure that the 0 and 1 values are not any meaningful flag
    //in the context of this function, it feels like a source of trouble later.
    
    //type_e = int, bool
    //As argument d, but the magnification filter argument.
    
    //type_f = NULL, int*
    //The colorkey argument.  Can be NULL (or omitted entirely with the same result).  If it
    //is included, the int* points to an array defining a four component color
    
    //type_g = int
    //The bit precision of the texture OpenGL should create
    Thanks!
    Ian
    Last edited by Geometrian; 01-21-2011 at 05:13 PM. Reason: forgot to document some stuff

  4. #4
    and the Hat of Guessing tabstop's Avatar
    Join Date
    Nov 2007
    Posts
    14,336
    1) What do your flags represent for d & e? I would imagine types of filters; and in that case "none" (i.e., false) seems like a reasonable input, but I'm not sure what you would do with "yes, please" (i.e. true). A default filter?

    2) Default options can only appear at the end (so no "omitt[ing] entirely"). That said, you can make multiple functions with the same name, as long as the function parameters don't match exactly. So you could do something like:
    Code:
    <template class T>
    func(T* arg_a,  int arg_b,  int arg_c,  int arg_d,  int arg_e,  int* arg_f,  int arg_g=8  );
    
    <template class T>
    func(T* arg_a,  int* arg_b,  int arg_c,  int arg_d,  int arg_e,  int* arg_f,  int arg_g=8  );
    
    func(int arg_b,  int arg_c,  int arg_d,  int arg_e,  int* arg_f,  int arg_g=8  );
    
    func(int arg_b,  int* arg_c,  int arg_d,  int arg_e,  int* arg_f,  int arg_g=8 );
    and so on.

    And if you don't want to duplicate code (and who does?) you could also, in each of these functions, "normalize" the input and then call
    Code:
    function_that_only_I_know_about( normalized data values );

  5. #5
    Registered User
    Join Date
    Dec 2010
    Posts
    19
    1) What do your flags represent for d & e? I would imagine types of filters; and in that case "none" (i.e., false) seems like a reasonable input, but I'm not sure what you would do with "yes, please" (i.e. true). A default filter?
    In OpenGL, it would probably just be GL_LINEAR (especially for magnification, where that's the only choice). But as I said, I'm concerned that casting the bool to an int might interfere with other flags. Any solutions other than writing out a whole bunch more constructors?
    Code:
    <template class T>
    func(T* arg_a,  int arg_b,  int arg_c,  int arg_d,  int arg_e,  int* arg_f,  int arg_g=8  );
    
    <template class T>
    func(T* arg_a,  int* arg_b,  int arg_c,  int arg_d,  int arg_e,  int* arg_f,  int arg_g=8  );
    
    func(int arg_b,  int arg_c,  int arg_d,  int arg_e,  int* arg_f,  int arg_g=8  );
    
    func(int arg_b,  int* arg_c,  int arg_d,  int arg_e,  int* arg_f,  int arg_g=8 );
    and so on.

    And if you don't want to duplicate code (and who does?) you could also, in each of these functions, "normalize" the input and then call
    Code:
    function_that_only_I_know_about( normalized data values );
    Yeah--the first time I tried doing this, I wrote out 64 constructors to cover all the possibilities. I was hoping there's a better way to do that, especially since not every argument depends on the type of every other argument.

    In the example above, if I omit argument arg_f, will it default to NULL?

    How do I determine which type the pointer for T is?

    Thanks,
    Ian

  6. #6
    Algorithm Dissector iMalc's Avatar
    Join Date
    Dec 2005
    Location
    New Zealand
    Posts
    6,318
    This looks like it might be a good candidate for using the Named Parameter Idiom
    My homepage
    Advice: Take only as directed - If symptoms persist, please see your debugger

    Linus Torvalds: "But it clearly is the only right way. The fact that everybody else does it some other way only means that they are wrong"

  7. #7
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    Quote Originally Posted by Geometrian View Post
    Code:
    func(  type_a arg_a,  type_b arg_b,  int arg_c,  type_d arg_d,  type_e arg_e,  type_f arg_f,  int arg_g=8  );
    
    //type_a = NULL, SDL_Surface*, float*, double*, unsigned char*, char*, std::string, std::string*
    //type_a is the data field.  One ought to be able to pass NULL to it, (or completely omit it
    //for the same result).  SDL_Surface* points to a data surface, while float*, double*, and
    //unsigned char* point to actual data.  char*, std::string, and std::string* are
    //filenames where data can be loaded from.  
    
    //type_b = int, int*
    //This is the rect argument, which specifies how the rectangular data is to be
    //interpreted.  It can either be a flag (int) or a pointer to a 4-element array defining
    //a rectangle
    
    //type_c = int
    //A format flag, specifying how OpenGL should use the data
    
    //type_d = int, bool
    //This is the minification filter argument.  It can either be a flag (int) or a bool.
    //Because the flags are arbitrarily defined, I'm hesitant to just cast the bool to an int.
    //Although I could probably ensure that the 0 and 1 values are not any meaningful flag
    //in the context of this function, it feels like a source of trouble later.
    
    //type_e = int, bool
    //As argument d, but the magnification filter argument.
    
    //type_f = NULL, int*
    //The colorkey argument.  Can be NULL (or omitted entirely with the same result).  If it
    //is included, the int* points to an array defining a four component color
    
    //type_g = int
    //The bit precision of the texture OpenGL should create
    To be candid, your design is broken. And you are attempting to misuse templates.

    I assume you are in control of the values, or your function maps various values as needed for calls to the OpenGL.

    bool values map to specific int values anyway (false to zero, true to 1), so I'd probably avoid overloading on int/bool anyway. So I doubt the distinction you make between int and bool is relevant. However, if you definitely want to eliminate that concern - consider passing type_c, type_d, type_e, and type_g as enumerated types (depending on ranges of values, they may be distinct enumerated types). All you need to do is map values of the enumerated type to appropriate int values inside the function.

    type_f only needs to be a pointer to int, with a default value of NULL. Since default arguments work right to left, ideally make it the last argument rather than the second last.

    With type_a, I wouldn't bother to support all three of char *, std::string, and std::string &. In practice, that overloading would just be confusing for users of your function anyway. Just support a char *. For user code, converting a std::string or a std::string * into a char * is trivial (using the c_str() method). The user of your function gains very little if you overload in that manner.

    After all that, I wouldn't bother using template functions at all. The reason I say that is that you said you want to do different things depending on the type - that is more easily done with normal function overloading than with templates.

    So, I'd just use straight function overloading, with a couple of helper functions to capture any common code between the overloaded functions. Those helper functions might be templated, but that's another story.

    But, if you insist on your main function being templated, only the first argument of the function needs to be related to a templated parameter. I consider that unnecessary, as really you only have three types to pass - providing three overloaded functions is sufficient.
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 4
    Last Post: 05-13-2011, 08:28 AM
  2. OpenGL DC Buffer Renders slow
    By Lane the Great in forum Game Programming
    Replies: 10
    Last Post: 01-07-2011, 07:52 PM
  3. We Got _DEBUG Errors
    By Tonto in forum Windows Programming
    Replies: 5
    Last Post: 12-22-2006, 05:45 PM
  4. call to function without prototype
    By lgbeeb in forum C Programming
    Replies: 2
    Last Post: 03-25-2003, 12:20 PM
  5. Replies: 5
    Last Post: 02-08-2003, 07:42 PM

Tags for this Thread