Cross-specialization template member function friends

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • emboss
    New Member
    • Feb 2008
    • 3

    Cross-specialization template member function friends

    If templates worked how I would like them to work, I'd be able to do the following:

    Code:
    template <int N>
    class A
    {
      private:
        int X[N];
      public:
        template <int M>
        void foo(const A<M> &Src) { X[N - 1] = Src.X[M - 1]; }
    
        template <int M>
        friend void A<M>::foo(const A<N> &Src);
    };
    
    int main(void)
    {
      A<3> A3;
      A<4> A4;
      A3.foo(A4);
    }
    It appears that templates do not in fact work like how I would like them to work, and GCC (4.1.2) comes up with a laundry-list of complaints:
    Code:
    templtest.cpp: In instantiation of "A<3>":
    templtest.cpp:16:   instantiated from here
    templtest.cpp:11: error: prototype for "void A<N>::foo(const A<3>&)" does not match any in class "A<N>"
    templtest.cpp:8: error: candidate is: template<int N> template<int M> void A::foo(const A<M>&)
    templtest.cpp: In instantiation of "A<4>":
    templtest.cpp:17:   instantiated from here
    templtest.cpp:11: error: prototype for "void A<N>::foo(const A<4>&)" does not match any in class "A<N>"
    templtest.cpp:11: error: candidates are: void A<N>::foo(const A<3>&)
    templtest.cpp:8: error:                 template<int N> template<int M> void A::foo(const A<M>&)
    templtest.cpp: In member function "void A<N>::foo(const A<M>&) [with int M = 4, int N = 3]":
    templtest.cpp:18:   instantiated from here
    templtest.cpp:5: error: "int A<4>::X [4]" is private
    templtest.cpp:8: error: within this context
    The only one that vaugely seems sensible is the "private" one, but only because it can't seem to figure out the friend line.

    After trying dozens of ways of coding this concept, the closest I've got to getting it to work is the following:
    Code:
    template <int N> class A;
    template <int N, int M> void do_foo(const A<M> &Src, A<N> &Dest);
    
    template <int N>
    class A
    {
      private:
        int X[N];
      public:
        template <int M>
        void foo(const A<M> &Src) { do_foo(Src, *this); }
    
        template <int M>
        friend void do_foo(const A<M> &Src, A<N> &Dest);
    };
    
    template <int N, int M> void do_foo(const A<M> &Src, A<N> &Dest)
    {
      Dest.X[N - 1] = Src.X[M - 1];
    }
    
    int main(void)
    {
      A<3> A3;
      A<4> A4;
      A3.foo(A4);
    }
    which "only" has a link error (do_foo doesn't get instantiated, so possibly this suppresses the other errors). Shuffling things around and/or adding prototypes to above the class definition doesn't help with this.

    Is there any way to get this to work, or should I just keep with the stop-gap solution of making X public?
  • weaknessforcats
    Recognized Expert Expert
    • Mar 2007
    • 9214

    #2
    Your final example compiles and links OK with Visual Studio.NET 2005.

    You might read up on bound vs unbound friend template functions. That would explain why you have to do what you did.

    Comment

    • emboss
      New Member
      • Feb 2008
      • 3

      #3
      Originally posted by weaknessforcats
      Your final example compiles and links OK with Visual Studio.NET 2005.
      Well, it's either a GCC bug or a MSVC bug then :) Which doesn't really help me that much as I need to do this using GCC. As an aside, it also causes a linker error on Sun's compiler.

      Originally posted by weaknessforcats
      You might read up on bound vs unbound friend template functions. That would explain why you have to do what you did.
      As I understand it, I've got a bound member function of an unbound class. As a result, the friend declarations effectively happen at the point of instantiation, and are, at that point, unbound (class) member function friends. The instantiation of A<3>::foo(cons t A<4> &) doesn't occur until the line "A3.foo(A4) ", at which point it it matched against A<4>'s friend delaration (which effectively happen on the preceeding line with the instantiation of A<4>) and everything flows along smoothly.

      However, upon careful re-reading of the C++ standard, it appears that there is a different problem: the standard doesn't allow for template class member functions to be friends of a template class. It only allows for template class member functions to be friends of a non-template class (14.5.3/6). Though I'm not sure why GCC doesn't flag the line as such, rather than reporting a lookup error.

      Are these interpretations correct?

      Comment

      • weaknessforcats
        Recognized Expert Expert
        • Mar 2007
        • 9214

        #4
        Member functions can't be friend functions.

        Comment

        • emboss
          New Member
          • Feb 2008
          • 3

          #5
          Originally posted by weaknessforcats
          Member functions can't be friend functions.
          Although this is how I read it, the developers of all the C++ compilers I could get my hands on (GCC, SunCC, MSVC) disagree. However, exactly what you can have as a member friend function varies substantially across the compilers. I got bored and brute-forced a whole lot of combinations. The results are quite interesting:

          Code:
          template <class T> class A;
          
          class X { };
          
          class B
          {
            public:
              void foo(void);
              template <class S> void baz(S &Param);
          };
          
          template <class T>
          class C
          {
            public:
              void foo(void);
              void bar(T &Param);
              template <class S> void baz(S &Param);
          };
          
          template <class T>
          class A
          {
            public:
              void foo(void);
              void bar(A<T> &Param);
              template <class S> void baz(S &Param);
          
              // G = GCC 4.1.2 (3.4.3 behaves identically).
              // S = SunCC 5.8
              // M = MSVC 2005
              //                                                     S G M
              friend void B::foo(void);                           // Y Y Y
              friend void B::baz<X>(X &);                         // Y Y N
              friend void B::baz<T>(T &);                         // Y Y N
              friend void B::baz<A<X> >(A<X> &);                  // Y Y N
              friend void B::baz<A<T> >(A<T> &);                  // N Y N
              template <class S> friend void B::baz(S &Param);    // Y Y Y
          
              friend void C<T>::foo(void);                        // Y Y Y
              friend void C<T>::bar(T &);                         // Y Y Y
              friend void C<T>::bar(X &);                         // Y Y N
              friend void C<T>::baz<X>(X &);                      // Y N N
              friend void C<T>::baz<T>(T &);                      // Y N N
              friend void C<T>::baz<A<X> >(A<X> &);               // Y N N
              friend void C<T>::baz<A<T> >(A<T> &);               // Y N N
              template <class S> friend void C<T>::baz(S &Param); // N Y N
              template <class S> friend void C<S>::baz(T &Param); // N N N
          
              template <class R>
              friend void C<R>::foo(void);                        // Y Y N
              template <class R>
              friend void C<R>::bar(T &);                         // Y N N
              template <class R>
              friend void C<R>::bar(X &);                         // N Y N
              template <class R>
              friend void C<R>::baz<X>(X &);                      // N N N
              template <class R>
              friend void C<R>::baz<T>(T &);                      // N N N
              template <class R>
              friend void C<R>::baz<A<X> >(A<X> &);               // N N N
              template <class R>
              friend void C<R>::baz<A<T> >(A<T> &);               // N N N
              template <class R>
              template <class S> friend void C<R>::baz(S &Param); // N Y N
              template <class R>
              template <class S> friend void C<S>::baz(R &Param); // Y Y N
          
              template <class R>
              friend void A<R>::foo(void);                        // Y Y N
              template <class R>
              friend void A<R>::bar(T &);                         // N N N
              template <class R>
              friend void A<R>::bar(X &);                         // N Y N
              template <class R>
              friend void A<R>::baz<X>(X &);                      // N N N
              template <class R>
              friend void A<R>::baz<T>(T &);                      // N N N
              template <class R>
              friend void A<R>::baz<A<X> >(A<X> &);               // N N N
              template <class R>
              friend void A<R>::baz<A<T> >(A<T> &);               // N N N
              template <class R>
              template <class S> friend void A<R>::baz(S &Param); // N Y N
              template <class R>
              template <class S> friend void A<S>::baz(R &Param); // Y Y N
          };
          
          int main(void)
          {
            A<X> AX;
            return 0;
          }
          Quite possibly my syntax is wrong for some of these options, or reordering/explicit instantiation would allow some of these to work - corrections would be appreciated. In any case, there is clearly a substantial difference in opinions as to how templates and friends should interact, and as mentioned all of these cases appear to be in violation of the standard.

          Where's a language lawyer when you need one ... :)

          Comment

          • weaknessforcats
            Recognized Expert Expert
            • Mar 2007
            • 9214

            #6
            The problem with member funcitons being friends is that the friend is seen by the compiler as an override with no access to the original funciton.

            You see, the override occurs when two member funcitons have the same name, argument list and amybe return type depending on whether you compiler supports covariance.

            So, to do the override, your class have to inherit the function and that obviates the need to call it a friend since you can just call the funciton.

            Comment

            Working...