Thread: template overloading question

  1. #1
    Registered User
    Join Date
    Dec 2008
    Posts
    14

    Question template overloading question

    I need to overload a function template so that it can handle between 1 to 6 arbitrary arguments. So I have these declarations:
    Code:
        template<typename T> 
        void f(int verbose, const T&);
        template<typename T1, typename T2> 
        void f(int verbose, const T1&, const T2&);
        template<typename T1, typename T2, typename T3> 
        void f(int verbose, const T1&, const T2&, const T3&);
        template<typename T1, typename T2, typename T3, typename T4> 
        void f(int verbose, const T1&, const T2&, const T3&, const T4&);
        template<typename T1, typename T2, typename T3, typename T4, typename T5> 
        void f(int verbose, const T1&, const T2&, const T3&, const T4&, const T5&);
        template<typename T1, typename T2, typename T3, typename T4, typename T5, typename T6> 
        void f(int verbose, const T1&, const T2&, const T3&, const T4&, const T5&, const T6&);
    I also need different handling for arrays, so I go with
    Code:
        template<typename T>
        void f(int verbose, const T a[], int size);
    Now the problem. When I call f with
    Code:
    double a[10];
    f(1, a, 10);
    it resolves to
    Code:
        template<typename T1, typename T2> 
        void f(int verbose, const T1&, const T2&);
    rather than the array template.

    I understand that in this case I should use a template specialization, but how do you do it when the specialization also involves a template typename?

    Thanks for any help.

  2. #2
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by plutino
    I understand that in this case I should use a template specialization, but how do you do it when the specialization also involves a template typename?
    Use template partial specialisation... but function templates cannot be partially specialised.

    You can convince the compiler to choose the function template you want by coercing the type:
    Code:
    double a[10];
    f(1, const_cast<const double*>(a), 10);
    But this seems ugly and error prone, so perhaps there is a better way.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  3. #3
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    No template specialization. Just overload. But choose the right type for overloading, because the one you have is not right - the array will prefer to be bound by reference rather than decay to a pointer.

    Code:
    template <typename T, unsigned N>
    void f(int verbose, const T (&ar)[N]);
    Notes:
    1) This works only for arrays with statically known size.
    2) No need for a size parameter, it's encoded in the type.
    3) This leads to one instantiation per array size, which, depending on your use pattern, may lead to code bloat. On the other hand, you can do it like this:
    Code:
    template <typename T, unsigned N> inline
    void f(int verbose, const T (&ar)[N])
    {
      f(verbose, &ar[0], N);
    }
    Add the pointer overload you had earlier.
    This tiny function is pretty much guaranteed to be inlined away, so no code bloat.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  4. #4
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    Maybe I just don't understand. Do you want an overload that really does only work with arrays? Do you want an overload that handles pointers and arrays separately from each other? Do you want an overload that handles pointers and arrays the same but different from other types?

    In any case, it is a really bad idea to even try this solely with function overloading. I get eighteen different results from six different compilers ranging from a totally nonsensical dump to compiler time errors of ambiguity all with the same source. (For example, one compiler let's the original pass with "good" results while another complains of ambiguity on twelve lines.)

    If you use a static method of a partially specialized class and traits you may accomplish whichever you wish and still get consistent results.

    Soma

    Code:
    #include <iostream>
    
    template <typename T> class flag1{flag1();};
    template <typename T> class flag2{flag2();};
    template <typename T> class flag3{flag3();};
    
    template <typename T1> void f1(int, const T1[]){std::cout << "f1-1\n";}
    template <typename T1> void f1(int, const T1&){std::cout << "f1-2\n";}
    template <typename T1, unsigned N> void f1(int, const T1(&)[N]){std::cout << "f1-3\n";}
    
    template <typename T1> void f2(int, const T1[]){std::cout << "f2-1\n";}
    template <typename T1> void f2(int, const T1&){std::cout << "f2-2\n";}
    template <typename T1, unsigned N> void f2(int, const T1(&)[N]){std::cout << "f2-3\n";}
    
    template <typename T1> void f3(int, const T1[], int) {std::cout << "f3-1\n";}
    template <typename T1> void f3(int, const T1&, int){std::cout << "f3-2\n";}
    template <typename T1, unsigned N> void f3(int, const T1(&)[N], int){std::cout << "f3-3\n";}
    
    template <typename T1> void f4(int, const T1[], int) {std::cout << "f4-1\n";}
    template <typename T1> void f4(int, const T1&, int){std::cout << "f4-2\n";}
    template <typename T1, unsigned N> void f4(int, const T1(&)[N], int){std::cout << "f4-3\n";}
    
    template <typename T1, typename T2> void f5(int, const T1[], const T2&){std::cout << "f5-1\n";}
    template <typename T1, typename T2> void f5(int, const T1&, const T2&){std::cout << "f5-2\n";}
    template <typename T1, typename T2, unsigned N> void f5(int, const T1(&)[N], const T2&){std::cout << "f5-3\n";}
    
    template <typename T1, typename T2> void f6(int, const T1[], const T2&){std::cout << "f6-1\n";}
    template <typename T1, typename T2> void f6(int, const T1&, const T2&){std::cout << "f6-2\n";}
    template <typename T1, typename T2, unsigned N> void f6(int, const T1(&)[N], const T2&){std::cout << "f6-3\n";}
    
    int main()
    {
       double a[10];
       double * b = new double[10];
       f1(1, a);
       f1(1, &a[0]);
       f1(1, b);
       f1(1, &b[0]);
       std::cout << '\n';
       f2(1, a);
       f2(1, &a[0]);
       f2(1, b);
       f2(1, &b[0]);
       std::cout << '\n';
       f3(1, a, 10);
       f3(1, &a[0], 10);
       f3(1, b, 10);
       f3(1, &b[0], 10);
       std::cout << '\n';
       f4(1, a, 10);
       f4(1, &a[0], 10);
       f4(1, b, 10);
       f4(1, &b[0], 10);
       std::cout << '\n';
       f5(1, a, 10);
       f5(1, &a[0], 10);
       f5(1, b, 10);
       f5(1, &b[0], 10);
       std::cout << '\n';
       f6(1, a, 10);
       f6(1, &a[0], 10);
       f6(1, b, 10);
       f6(1, &b[0], 10);
       std::cout << '\n';
       return(0);
    }

  5. #5
    Registered User
    Join Date
    Dec 2008
    Posts
    14
    Thanks. Multiple instantiation is not an issue, since these are all two-statement inline functions. However, the problem is that I don't know the array size ahead of time. If I change your prototype to
    Code:
    template <typename T> void f(int verbose, const T(&ar)[], int size)
    will it still resolve?

    Quote Originally Posted by CornedBee View Post
    No template specialization. Just overload. But choose the right type for overloading, because the one you have is not right - the array will prefer to be bound by reference rather than decay to a pointer.

    Code:
    template <typename T, unsigned N>
    void f(int verbose, const T (&ar)[N]);
    Notes:
    1) This works only for arrays with statically known size.
    2) No need for a size parameter, it's encoded in the type.
    3) This leads to one instantiation per array size, which, depending on your use pattern, may lead to code bloat. On the other hand, you can do it like this:
    Code:
    template <typename T, unsigned N> inline
    void f(int verbose, const T (&ar)[N])
    {
      f(verbose, &ar[0], N);
    }
    Add the pointer overload you had earlier.
    This tiny function is pretty much guaranteed to be inlined away, so no code bloat.

  6. #6
    Registered User
    Join Date
    Dec 2008
    Posts
    14
    Sorry about the confusion. I don't process any single pointers, only need to overload it to work with arrays. I haven't thought about a class implementation, but these functions are already class methods, and I need them to be inline to avoid any performance overhead (in fact, they are used in a time-consuming numerical simulation code to report debug errors). I'm not sure how well a class implementation works.


    Quote Originally Posted by phantomotap View Post
    Maybe I just don't understand. Do you want an overload that really does only work with arrays? Do you want an overload that handles pointers and arrays separately from each other? Do you want an overload that handles pointers and arrays the same but different from other types?

    In any case, it is a really bad idea to even try this solely with function overloading. I get eighteen different results from six different compilers ranging from a totally nonsensical dump to compiler time errors of ambiguity all with the same source. (For example, one compiler let's the original pass with "good" results while another complains of ambiguity on twelve lines.)

    If you use a static method of a partially specialized class and traits you may accomplish whichever you wish and still get consistent results.

    Soma

    Code:
    #include <iostream>
    
    template <typename T> class flag1{flag1();};
    template <typename T> class flag2{flag2();};
    template <typename T> class flag3{flag3();};
    
    template <typename T1> void f1(int, const T1[]){std::cout << "f1-1\n";}
    template <typename T1> void f1(int, const T1&){std::cout << "f1-2\n";}
    template <typename T1, unsigned N> void f1(int, const T1(&)[N]){std::cout << "f1-3\n";}
    
    template <typename T1> void f2(int, const T1[]){std::cout << "f2-1\n";}
    template <typename T1> void f2(int, const T1&){std::cout << "f2-2\n";}
    template <typename T1, unsigned N> void f2(int, const T1(&)[N]){std::cout << "f2-3\n";}
    
    template <typename T1> void f3(int, const T1[], int) {std::cout << "f3-1\n";}
    template <typename T1> void f3(int, const T1&, int){std::cout << "f3-2\n";}
    template <typename T1, unsigned N> void f3(int, const T1(&)[N], int){std::cout << "f3-3\n";}
    
    template <typename T1> void f4(int, const T1[], int) {std::cout << "f4-1\n";}
    template <typename T1> void f4(int, const T1&, int){std::cout << "f4-2\n";}
    template <typename T1, unsigned N> void f4(int, const T1(&)[N], int){std::cout << "f4-3\n";}
    
    template <typename T1, typename T2> void f5(int, const T1[], const T2&){std::cout << "f5-1\n";}
    template <typename T1, typename T2> void f5(int, const T1&, const T2&){std::cout << "f5-2\n";}
    template <typename T1, typename T2, unsigned N> void f5(int, const T1(&)[N], const T2&){std::cout << "f5-3\n";}
    
    template <typename T1, typename T2> void f6(int, const T1[], const T2&){std::cout << "f6-1\n";}
    template <typename T1, typename T2> void f6(int, const T1&, const T2&){std::cout << "f6-2\n";}
    template <typename T1, typename T2, unsigned N> void f6(int, const T1(&)[N], const T2&){std::cout << "f6-3\n";}
    
    int main()
    {
       double a[10];
       double * b = new double[10];
       f1(1, a);
       f1(1, &a[0]);
       f1(1, b);
       f1(1, &b[0]);
       std::cout << '\n';
       f2(1, a);
       f2(1, &a[0]);
       f2(1, b);
       f2(1, &b[0]);
       std::cout << '\n';
       f3(1, a, 10);
       f3(1, &a[0], 10);
       f3(1, b, 10);
       f3(1, &b[0], 10);
       std::cout << '\n';
       f4(1, a, 10);
       f4(1, &a[0], 10);
       f4(1, b, 10);
       f4(1, &b[0], 10);
       std::cout << '\n';
       f5(1, a, 10);
       f5(1, &a[0], 10);
       f5(1, b, 10);
       f5(1, &b[0], 10);
       std::cout << '\n';
       f6(1, a, 10);
       f6(1, &a[0], 10);
       f6(1, b, 10);
       f6(1, &b[0], 10);
       std::cout << '\n';
       return(0);
    }

  7. #7
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    Quote Originally Posted by plutino View Post
    Thanks. Multiple instantiation is not an issue, since these are all two-statement inline functions. However, the problem is that I don't know the array size ahead of time. If I change your prototype to
    Code:
    template <typename T> void f(int verbose, const T(&ar)[], int size)
    will it still resolve?
    No. You're declaring a reference to a pointer here, and it will resolve even less than the original.

    Either you know the size of the array ahead of time, or you're not actually working with arrays. Or you're using variable-sized arrays in C++, but you really shouldn't.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  8. #8
    Registered User
    Join Date
    Dec 2008
    Posts
    14
    Well, these "arrays" are dynamically allocated with "new double[]". their sizes are not going to change once allocated. Maybe I should go with a non-uniform argument list, say, move the "int verbose" to the end of the argument list.

    Quote Originally Posted by CornedBee View Post
    No. You're declaring a reference to a pointer here, and it will resolve even less than the original.

    Either you know the size of the array ahead of time, or you're not actually working with arrays. Or you're using variable-sized arrays in C++, but you really shouldn't.

  9. #9
    Registered User
    Join Date
    Dec 2008
    Posts
    14
    By the way, is there a better way to handle the variable arguments function reloading than the pyramid I used? I don't want to go with the old C va_list method.

    Quote Originally Posted by CornedBee View Post
    No. You're declaring a reference to a pointer here, and it will resolve even less than the original.

    Either you know the size of the array ahead of time, or you're not actually working with arrays. Or you're using variable-sized arrays in C++, but you really shouldn't.

  10. #10
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    But then you have a pointer, so the pointer overload should catch. Unless, perhaps, the pointer is to non-const, then the conversion could mean that the other version is preferred.

    Can I recommend using vectors instead of self-managed dynamic arrays? The problem would go away.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  11. #11
    Registered User
    Join Date
    Dec 2008
    Posts
    14
    This sounds like a dead end. I'm trying to plug this debug class in a old code set. Most part of the code has already used a stripped-down version of the vector class, but some older parts still use dynamic array.

    So the major problem is caused by trying to overload a function with a pointer argument?

    Quote Originally Posted by CornedBee View Post
    But then you have a pointer, so the pointer overload should catch. Unless, perhaps, the pointer is to non-const, then the conversion could mean that the other version is preferred.

    Can I recommend using vectors instead of self-managed dynamic arrays? The problem would go away.

  12. #12
    The larch
    Join Date
    May 2006
    Posts
    3,573
    By the way, is there a better way to handle the variable arguments function reloading than the pyramid I used? I don't want to go with the old C va_list method.
    It should be possible when compilers implement the variadic templates feature.

    And as far as I understand va_list, it won't necessarily work with user-defined classes, so you still can't use it in place of what you have.

    One question is what you are actually using this for (there may be different solutions altogether).

    Also, in C++, an array is a type bound with its size.

    Code:
    int this_is_array[10];
    When passed to functions it collapses into a pointer (loses the associated size information). If an array is allocated dynamically, all you have is a pointer to start with.
    I might be wrong.

    Thank you, anon. You sure know how to recognize different types of trees from quite a long way away.
    Quoted more than 1000 times (I hope).

  13. #13
    Registered User
    Join Date
    Dec 2008
    Posts
    14
    Thanks for the clarifications, especially on C++ array definition. These functions are used by a Debug class to dump random user data into a file stream.

    Quote Originally Posted by anon View Post
    It should be possible when compilers implement the variadic templates feature.

    And as far as I understand va_list, it won't necessarily work with user-defined classes, so you still can't use it in place of what you have.

    One question is what you are actually using this for (there may be different solutions altogether).

    Also, in C++, an array is a type bound with its size.

    Code:
    int this_is_array[10];
    When passed to functions it collapses into a pointer (loses the associated size information). If an array is allocated dynamically, all you have is a pointer to start with.

  14. #14
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    Screw It. Look at the example; if the situation is as you've described, it will work.

    Soma

    Code:
    #include <iostream>
    
    struct false_type{};
    struct true_type{};
    
    template
    <
       typename type_F
    >
    struct is_pointer_or_array
    {
       typedef false_type type;
    };
    
    template
    <
       typename type_F
    >
    struct is_pointer_or_array
    <
       type_F *
    >
    {
       typedef true_type type;
    };
    
    template
    <
       typename type_F,
       unsigned size_F
    >
    struct is_pointer_or_array
    <
       type_F[size_F]
    >
    {
       typedef true_type type;
    };
    
    template
    <
       typename is_parameter_a_pointer_or_array
    >
    struct test_implementation
    {
       template
       <
          typename type_FN
       >
       inline static void execute
       (
          int verbose_f,
          const type_FN & t_f,
          int not_size_but_something_probably_important_f
       )
       {
          std::cout << "second parameter is not a pointer or array\n";
       }
    };
    
    template <>
    struct test_implementation
    <
       true_type
    >
    {
       template
       <
          typename type_FN
       >
       inline static void execute
       (
          int verbose_f,
          const type_FN & t_f,
          int size_f
       )
       {
          std::cout << "second parameter is a pointer or array\n";
       }
    };
    
    template
    <
       typename type_F
    >
    void test
    (
       const int verbose_f,
       const type_F & t_f,
       int maybe_size_or_maybe_something_else_f
    )
    {
       typedef is_pointer_or_array<type_F>::type is_type_pointer;
       typedef test_implementation<is_type_pointer> implementation;
       implementation::execute(verbose_f, t_f, maybe_size_or_maybe_something_else_f);
    }
    
    template
    <
       typename type_00_F,
       typename type_01_F
    >
    void test
    (
       const int verbose_f,
       const type_00_F & t00_f,
       const type_01_F & t01_f
    )
    {
       std::cout << "third parameter was not an `int'\n";
    }
    
    int main()
    {
       double a;
       double b[10];
       double * c(new double[10]);
       test(0, a, 10);
       test(0, b, 10);
       test(0, c, 10);
       test(0, a, 10.0);
       test(0, b, 10.0);
       test(0, c, 10.0);
       delete[] c;
       return(0);
    }
    Last edited by phantomotap; 02-25-2009 at 03:31 PM. Reason: swapped example for explanation

  15. #15
    Registered User
    Join Date
    Dec 2008
    Posts
    14

    Thumbs up

    wow, that's a clever implementation. Thank you. But it's too complicated for a simple implementation that I'm looking for. I will just skip C arrays since I will probably never use them directly again.

    Quote Originally Posted by phantomotap View Post
    Screw It. Look at the example; if the situation is as you've described, it will work.

    Soma

    Code:
    #include <iostream>
    
    struct false_type{};
    struct true_type{};
    
    template
    <
       typename type_F
    >
    struct is_pointer_or_array
    {
       typedef false_type type;
    };
    
    template
    <
       typename type_F
    >
    struct is_pointer_or_array
    <
       type_F *
    >
    {
       typedef true_type type;
    };
    
    template
    <
       typename type_F,
       unsigned size_F
    >
    struct is_pointer_or_array
    <
       type_F[size_F]
    >
    {
       typedef true_type type;
    };
    
    template
    <
       typename is_parameter_a_pointer_or_array
    >
    struct test_implementation
    {
       template
       <
          typename type_FN
       >
       inline static void execute
       (
          int verbose_f,
          const type_FN & t_f,
          int not_size_but_something_probably_important_f
       )
       {
          std::cout << "second parameter is not a pointer or array\n";
       }
    };
    
    template <>
    struct test_implementation
    <
       true_type
    >
    {
       template
       <
          typename type_FN
       >
       inline static void execute
       (
          int verbose_f,
          const type_FN & t_f,
          int size_f
       )
       {
          std::cout << "second parameter is a pointer or array\n";
       }
    };
    
    template
    <
       typename type_F
    >
    void test
    (
       const int verbose_f,
       const type_F & t_f,
       int maybe_size_or_maybe_something_else_f
    )
    {
       typedef is_pointer_or_array<type_F>::type is_type_pointer;
       typedef test_implementation<is_type_pointer> implementation;
       implementation::execute(verbose_f, t_f, maybe_size_or_maybe_something_else_f);
    }
    
    template
    <
       typename type_00_F,
       typename type_01_F
    >
    void test
    (
       const int verbose_f,
       const type_00_F & t00_f,
       const type_01_F & t01_f
    )
    {
       std::cout << "third parameter was not an `int'\n";
    }
    
    int main()
    {
       double a;
       double b[10];
       double * c(new double[10]);
       test(0, a, 10);
       test(0, b, 10);
       test(0, c, 10);
       test(0, a, 10.0);
       test(0, b, 10.0);
       test(0, c, 10.0);
       delete[] c;
       return(0);
    }

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. template question
    By wxjeacen in forum C++ Programming
    Replies: 6
    Last Post: 02-25-2009, 02:18 AM
  2. Replies: 7
    Last Post: 11-10-2007, 05:17 AM
  3. error: template with C linkage
    By michaels-r in forum C++ Programming
    Replies: 3
    Last Post: 05-17-2006, 08:11 AM
  4. user defined template question
    By kocika73 in forum C++ Programming
    Replies: 3
    Last Post: 04-25-2006, 05:01 AM
  5. Class Template Trouble
    By pliang in forum C++ Programming
    Replies: 4
    Last Post: 04-21-2005, 04:15 AM

Tags for this Thread