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;
}