2

Warning, C++ hating ahead... I have seen here slightly different variations of this issue, here is my take

namespace space {
}

template <typename T> struct C
{
   void foo() {
      using namespace space;
      bar(t);
   }
   T t;
};

class A {
};

namespace space {

void bar(const A& a){
}

}

int main()
{
   C<A> c;
   c.foo();
   return 0;
}

if bar is not inside a namespace, everything compiles ok. Putting a namespace breaks compilation with gcc. What is more interesting - gcc finds the function, but just don't feel like using it:

ConsoleApplication1.cpp: In instantiation of 'void C<T>::foo() [with T = A]':
ConsoleApplication1.cpp:26:10:   required from here
ConsoleApplication1.cpp:8:8: error: 'bar' was not declared in this scope, and no declarations were found by argument-dependent lookup at the point
instantiation [-fpermissive]
   bar(t);
        ^
ConsoleApplication1.cpp:18:6: note: 'void space::bar(const A&)' declared here, later in the translation unit
 void bar(const A& a){

Is there any sane reason for this behaviour, except that the standard says so? And is there any switch I can make gcc accept this code, since it seems there is no technical issue, and I do not see why such code should not work from a pure language standpoint.

jj99
  • 300
  • 1
  • 11
  • Unlike template parameters namespace symbols must be visible at invocation. Hence there is no way to make the code compile. –  May 02 '14 at 10:58
  • @DieterLücking, except using the `-fpermissive` switch shown in the error. But that's not a good idea. – Jonathan Wakely May 02 '14 at 13:21

3 Answers3

4

templates are not macros. Name lookup of symbols is done in two contexts: first, where you wrote the template, and second pass of only argument-dependent lookup (ADL, or Koeing Lookup) when you pass the template a type (when the template "factory" is instantiated into an actual class or function).

When the function is in the same namespace as A, it is found via ADL. When it is not, it will not be. So when you moved the bar function into space::bar, it was no longer ADL-found with an argument of type A.

This is intentional, to both prevent your template from meaning completely different things at different spots, and to allow non-member interface extensions to types in their own namespace only.

Either use a traits class, stick such helper functions in the same namespace as the type, or pass in functors.

A traits class is probably easiest. Create a class with a static function foo (if you want a default implementation: otherwise leave empty). Call it from your template. Specialize it for A. Now you can implement special behavior for your A type at that point -- it could even call your space::bar function if that function is in view.

Putting bar in the same namespace as A is another solution, and I find it scales better than traits classes. Many of my personal traits classes end up falling back on an ADL lookup, as that lets me inject the handling code right next to where I define A. (The use of traits classes lets me also have my trait handle things in std, where I am not allowed to inject functions for ADL purposes for types living in std (you can inject ADL functions into std, but only for your own types))

The functor solution is how std::map works -- while it falls back on std::less<T> which falls back on operator<, it takes a functor that lets you specify how the key type should be compared within this map.

Community
  • 1
  • 1
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Finally, an answer that addressees the main point of this question: why? Perhaps this answer would be improved by an example of the sort of strange behaviour that could occur if name-lookup was done with the `-fpermissive` rules. – Mankarse May 02 '14 at 11:36
2

Is there any sane reason for this behaviour, except that the standard says so?

Sane or not, C++ usually requires names to be declared before use. You don't declare bar before using it; specifically, using namespace space only imports names that have been declared at that point, and not bar.

(If you care about the reason, it's because C++ inherited its declaration rules from the languages of half a century ago, when computers were somewhat primitive. Rules like this allowed compilation in a single pass, so that the compiler didn't have to keep waiting for the operator to put the stack of punch cards back into the hopper. Or something like that; I'm not quite sure how computers worked back then.)

And is there any switch I can make gcc accept this code

As the error message says, -fpermissive. But your code won't be portable if you don't stick to the standard.

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • In addition to the code not being portable, using `-fpermissive`, changes a *lot* more than just name lookup in templates. It also allows all sort of non-standard conversions and weirdness that used to be allowed in pre-standard C++, losing some type-safety (I don't know details; I'm not quite sure how C++ worked back then :-) – Jonathan Wakely May 02 '14 at 13:26
  • My real code is actually more complicated and I already have void bar overloads in 'space', so even -fpermissive will not save me, since gcc will totally ignore other bar overloads. I'm just angry that c++ templates are still not very usable even with c++11. Any company that I've worked are using rarely templates and in their simplest form... – jj99 May 02 '14 at 13:42
0

This error has nothing to do with namespaces or templates at all, but is the standard error of using a function before it has been declared. Just as you cannot do:

void caller() { callee(); }
void callee() {}

But must instead do:

void callee() {}     
void caller() { callee(); }

Or:

void callee();     
void caller() { callee(); }
void callee() {}

... the same applies for more complicated functions involving templates and namespaces. Note that if you reorder slightly, it works just fine:

class A {
};

namespace space {
void bar(const A& a){
}
}

template <typename T> struct C
{
  void foo() {
    using namespace space;
    bar(t);
 }
 T t;
};


int main()
{
  C<A> c;
  c.foo();
  return 0;
 }
Michael Aaron Safyan
  • 93,612
  • 16
  • 138
  • 200
  • Although you're correct that the error given for the original code isn't due to namespaces or templates, the fact that it compiles when `bar` is moved to the global namespace _is_ related to namespaces and templates, because lookup of dependent names in templates considers associated namespaces, and the global namespace is in an associated namespace of `A`. – Jonathan Wakely May 02 '14 at 13:19