Reputation: 24719
I'm confused about function-name lookup in the context of a template. I know that the compiler delays argument-dependent identifier lookup in templated code until the template is instantiated. This means you can sometimes have syntax errors or call non-existent functions within templated code, and the compiler won't complain unless you actually instantiate the template.
However, I've found a discrepancy between different compilers, and I'm interested in knowing what the standard itself requires.
Consider the following code:
#include <iostream>
class Foo
{
public:
template <class T>
void bar(T v)
{
do_something(v);
}
};
void do_something(std::string s)
{
std::cout << "do_something(std::string)" << std::endl;
}
void do_something(int x)
{
std::cout << "do_something(int)" << std::endl;
}
int main()
{
Foo f;
f.bar("abc");
f.bar(123);
}
Note that the template member function Foo::bar
calls a non-argument-dependent global function called do_something
, which hasn't even been declared yet.
Yet, GCC 4.6.3 will happily compile the above program. When run, the output is:
do_something(std::string)
do_something(int)
So, it looks as though the compiler delayed identifier lookup until after the template was instantiated, at which point it was able to find do_something
.
In contrast, GCC 4.7.2 will not compile the above program. It produces the following error:
test.cc: In instantiation of ‘void Foo::bar(T) [with T = const char*]’:
test.cc:27:13: required from here
test.cc:10:3: error: ‘do_something’ was not declared in this scope, and no declarations were found by argument-dependent lookup at the point of instantiation [-fpermissive]
test.cc:19:6: note: ‘void do_something(int)’ declared here, later in the translation unit
So, GCC 4.7.2 is aware that do_something
is later declared, but refuses to compile the program because do_something
is not argument-dependent.
So, I'm assuming GCC 4.7.2 is probably correct here, and GCC 4.6.3 is incorrect. So presumably, I'd need to declare do_something
before Foo::bar
is defined. The problem with this is suppose I want to allow users of my class Foo
to extend the behavior of Foo::bar
by implementing their own overloads of do_something
. I'd need to write something like:
#include <iostream>
template <class T>
void do_something(T v)
{
std::cout << "do_something(T)" << std::endl;
}
class Foo
{
public:
template <class T>
void bar(T v)
{
do_something(v);
}
};
void do_something(int x)
{
std::cout << "do_something(int)" << std::endl;
}
int main()
{
Foo f;
f.bar("abc");
f.bar(123);
}
The problem here, is that the overloads of do_something
are not visible from within Foo::bar
, and thus never called. So even if I call do_something(int)
, it will call do_something(T)
rather than the overload for int
. Thus, with both GCC 4.6.3 and GCC 4.7.2, the above program outputs:
do_something(T)
do_something(T)
So what are some solutions here? How can I allow users to extend Foo::bar
by implementing their own overloads of do_something
?
Upvotes: 3
Views: 1631
Reputation: 15069
As far as overloading do_something
goes, you need to specialize your original template:
template<>
void do_something<int>(int x) {
std::cout << "do_something(int)" << std::endl;
}
Edit : As @MatthieuM. pointed out, function template specialization can yield weird results if you also need to overload the function (and at some point you'll probably need to, since function templates can't be partially specialized). See Matthieu's link to Herb Sutter's article Why Not Specialize Function Templates? for a full explanation.
What is recommended instead is to use a static function wrapped in a struct, which allows partial specialization and removes that name resolution problem that comes with overloaded function templates.
template<typename T>
struct DoSomething {
static void do_something(T v) {
std::cout << "do_something(T)" << std::endl;
}
};
struct Foo
{
template <class T>
void bar(T v) {
DoSomething<T>::do_something(v);
}
};
// Now you can specialize safely
template<>
struct DoSomething<int> {
static void do_something(int v) {
std::cout << "do_something(int)" << std::endl;
}
};
Upvotes: 3