简体   繁体   中英

problems with template friend of template class

I ran into what to me looks like an inconsistency of the c++ compiler. In the following example code

#include <vector>
namespace test {
  class A : std::vector<int>
  {
    template<typename F>
    friend void bar(A const&a, F f) { for(auto i:a) f(i); }
    template<int K, typename F>
    friend void foo(A const&a, F f) { for(auto i:a) if(i&K) f(i); }
  };
}
int sum(test::A const&a)
{
  int s=0;
  foo<2>(a,[&s](int i) { s+=i; } );    // <-- error here
  bar   (a,[&s](int i) { s+=i; } );    // <-- but not here
  return s;
}

gcc (4.7.0, using std=c++11) complains about " foo was not declared in this scope" (and suggests test::foo as alternative), but happily compiles the usage of bar in the next line. Now both, foo and bar are injected into the namespace test via their friend declaration, so neither should really be present in the global namespace.

Q1 Am I mistaken, or is this a new twist of c++11, or is gcc misbehaving?

Of course, the problem is avoided if I simply inject using directives into the global namespace. However, if I make A a template,

#include <vector>
namespace test {
  template<typename T>
  class A : std::vector<T>
  {
    template<typename F>
    friend void bar(A const&a, F f) { for(auto i:a) f(i); }
    template<int K, typename F>
    friend void foo(A const&a, F f) { for(auto i:a) if(i&K) f(i); }
  };
}
using test::foo;          // does not avoid compilation error
using test::bar;          // does not avoid compilation error
int sum(test::A<int> const&a)
{
  int s=0;
  foo<2>(a,[&s](int i) { s+=i; } );
  bar   (a,[&s](int i) { s+=i; } );
  return s;
}

gcc complains again. Either (without the using directives) that " foo was not declared in this scope" (but again happily compiles bar , though does not suggest test::foo ) or (with the using directive) that " test::foo has not been declared" (and the same for test::bar ) at the point of the using directive.

Q2 This looks to me like a compiler error, as neither with or without using directive can I call test::foo . Or perhaps I there is something about C++ that I missed?

Finally, I tried to move the friend definition outside the class as in

namespace test {
  template<typename T>
  class A : std::vector<int>
  {
    template<int K, typename F>
    friend void foo(A const&a, F f);
    template<typename F>
    friend void bar(A const&a, F f) { for(auto i:a) f(i); }
  };

  template<int K, typename T, typename F>
  void foo(A<T> const&a, F f) { for(auto i:a) if(i&K) f(i); }

}
using test::foo;

when gcc again complains, this time claiming that void test::foo(const test::A<T>&, F) is used but never defined ... So Q3 what's wrong?

Answers to any sub-question welcome.

Q1:

Am I mistaken, or is this a new twist of c++11, or is gcc misbehaving?

No, this is normal behavior. From Paragraph 14.8.1/8 of the C++11 Standard:

For simple function names, argument dependent lookup (3.4.2) applies even when the function name is not visible within the scope of the call. This is because the call still has the syntactic form of a function call (3.4.1). But when a function template with explicit template arguments is used, the call does not have the correct syntactic form unless there is a function template with that name visible at the point of the call . If no such name is visible, the call is not syntactically well-formed and argument-dependent lookup does not apply. If some such name is visible, argument dependent lookup applies and additional function templates may be found in other namespaces. [ Example:

namespace A {
    struct B { };
    template<int X> void f(B);
}
namespace C {
    template<class T> void f(T t);
}
void g(A::B b) {
    f<3>(b); // ill-formed: not a function call
    A::f<3>(b); // well-formed
    C::f<3>(b); // ill-formed; argument dependent lookup
    // applies only to unqualified names
    using C::f;
    f<3>(b); // well-formed because C::f is visible; then
    // A::f is found by argument dependent lookup
}

—end example ]


Q2:

This looks to me like a compiler error, as neither with or without using directive can I call test::foo. Or perhaps I there is something about C++ that I missed?

If your class becomes a class template which you never instantiate, then the compiler will never perform the second-phase name lookup which would occur when instantiating A<> , so it will it never find out that there are two friend functions declared in it.

If you introduced, for instance, an explicit instantiation of your template before the using declarations, you should see things changing:

template class test::A<int>;

Alternatively, you could just change the definition of A so that it only declares , and doesn't define, the two friend function templates, and provide an out-of-class definition for those function templates. Which is, I guess, what you actually tried to do. But...

Q3:

gcc again complains, this time claiming that void test::foo(const test::A&, F) is used but never defined... So what's wrong?

The problem is that you are not declaring as friend the same function that you are later defining: notice, that the function you defined takes one additional argument ( T ). Fix your declaration, and you'll see the program compile:

namespace test 
{
    template<typename T>
    class A : std::vector<int>
    {
        template<int K, typename C, typename F>
        //              ^^^^^^^^^^  (can't use T here, it would shadow
        //                           the class's template parameter)
        friend void foo(A<C> const&a, F f);
    };

    template<int K, typename C, typename F>
    void foo(A<C> const&a, F f) 
    { for(auto i:a) if(i&K) f(i); }
}

using test::foo; // Just don't remove this, or we will be back in Q1 ;-)

CONCLUSION:

Thus, after all the necessary modifications, this is how your program will look like:

#include <vector>

namespace test
{
    template<typename T>
    class A : std::vector<T>
    {
        template<typename F, typename C>
        friend void bar(A<C> const&a, F f);

        template<int K, typename F, typename C>
        friend void foo(A<C> const&a, F f);
    };

    template<typename F, typename C>
    void bar(A<C> const&a, F f) { for(auto i:a) f(i); }

    template<int K, typename F, typename C>
    void foo(A<C> const&a, F f) { for(auto i:a) if(i&K) f(i); }
}

using test::foo;
using test::bar;

int sum(test::A<int> const& a)
{
    int s=0;
    foo<2>(a,[&s](int i) { s+=i; } );
    bar   (a,[&s](int i) { s+=i; } );

    return s;
}

Your problem and the answer to your questions is called ADL and the rules for when it is applied. It's not new in C++11 and it's not a problem with GCC.

Q1: You have a parameter a of type test::A (in the first example), hence ADL (argument dependent lookup) looks for methods in namespace test , but only for non-template calls . This is why foo<2> (a template call) is not found and bar is.

Q2: Answered after Q3, see below.

Q3: Your function definition for test::foo does not define the function you declared as a friend of test::A<T> . Change it to

namespace test
{
  template<typename T>
  class A;

  template<int K, typename F,typename T>
  void foo(A<T> const&a, F f);

  template<typename T>
  class A : std::vector<int>
  {
    template<int K, typename F,typename U>
    friend void foo(A<U> const&a, F f);
    template<typename F>
    friend void bar(A const&a, F f) { for(auto i:a) f(i); }
  };

  template<int K, typename F,typename T>
  void foo(A<T> const&a, F f) { for(auto i:a) if(i&K) f(i); }
}
using test::foo;

Q2: Similar to Q3, you can fix it like this:

#include <vector>
namespace test {
  template<typename T>
  class A;

  template<typename F,typename T>
  void bar(A<T> const&a, F f);
  template<int K, typename F,typename T>
  void foo(A<T> const&a, F f);

  template<typename T>
  class A : std::vector<T>
  {
    template<typename F,typename U>
    friend void bar(A<U> const&a, F f);
    template<int K, typename F,typename U>
    friend void foo(A<U> const&a, F f);
  };

  template<typename F,typename U>
  void bar(A<U> const&a, F f) { for(auto i:a) f(i); }
  template<int K, typename F,typename U>
  void foo(A<U> const&a, F f) { for(auto i:a) if(i&K) f(i); }
}
using test::foo;
using test::bar;
int sum(test::A<int> const&a)
{
  int s=0;
  foo<2>(a,[&s](int i) { s+=i; } );
  bar   (a,[&s](int i) { s+=i; } );
  return s;
}

Andy already explained why your original example does not work.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM