Given the definition of Foo
below, why a call to
Foo{2}();
is possible only if draw(int);
is declared before Foo
definition?
Ok, apparently a very similar example is shown in C++ Templates - The Complete Guide in §14.3.2, and the reason is roughly that when a tempalate is first seen, any unqualified dependent name (e.g. the draw
in operator()
below, which is dependent because t
is of type the template parameter T
) is looked up. In my example, at that point, draw(int)
is not visible yet. Later, when the call Foo{2}();
is parsed, only ADL is peformed, and so draw(int)
is not found because the set of namespaces associated to int
is empty.
But how do I understand this from the standard? I suppose the answer is in [basic.lookup], but can anybody guide me to decypher it?
Consider
these headers:
draw
function on an object of the template type,
// foo.hpp
template<typename T>
struct Foo {
T t;
void operator()() {
draw(t);
}
};
namespace
d class, with associated draw
function,
// bar.hpp
namespace bar {
struct Bar {};
void draw(Bar const&);
}
draw
for int
s,
// drawInt.hpp
void draw(int);
the corresponding cpp files:
// the cpp files
void draw(int) {
std::cout << "int" << std::endl;
}
namespace bar {
void draw(Bar const&){
std::cout << "Bar" << std::endl;
}
}
and the main
,
// main.cpp
#include "drawInt.hpp"
#include "bar.hpp"
#include "foo.hpp"
int main() {
Foo{bar::Bar{}}();
Foo{2}();
}
Compiling and executing with
g++ -std=c++20 *.cpp -O0 -o main && ./main
is successful and prints
Bar
int
the call to void bar::draw(bar::Bar)
is possible because of ADL, and we don't even really need bar::Bar
and bar::draw
be declared in any way before Foo
for the code to work, i.e. changing
#include "bar.hpp"
#include "foo.hpp"
to
#include "foo.hpp"
#include "bar.hpp"
has no effect on the program (I get the same exact binary). After all, the inside of operator()
can't be even be meaningfully looked at by the compiler, before the line
Foo{bar::Bar{}}();
triggers template type deduction of T
.
On the other hand, the call to void draw(int)
that corresponds to
Foo{2}();
is not resolved via ADL.
But why does that imply that the declaration of that overload of draw
must be visible before the definition of Foo
, which in turns means that the order of #include
s in main
because important?
I mean, by the time
Foo{2}();
is seen, void draw(int)
is visible.
And, whatever the reason is, is that a symptom of bad design?
But then what's the benefit of ADL, if I can't use it seamlessly together non-ADL calls for builtin types? In this respect, I'm thinking about Sean Parent runtime concept idiom (the ADL call is 35:57 of Better Code: Runtime Polymorphism - Sean Parent.
draw(t)
is a dependent call ([temp.dep.general]/2), which makes the function name a dependent name in this context. As specified in [basic.lookup.argdep]/4, when argument-dependent lookup is performed for a dependent name, it searches both in the definition context and the instantiation context. Thus, as long as the appropriate draw
overload is declared before the point of instantiation, it can be found by ADL.
However, ADL only looks in associated namespaces; it doesn't duplicate the behaviour of ordinary unqualified name lookup. Because int
is a fundamental type, it has no associated namespaces, so when the argument to a dependent call has type int
, there is nothing for ADL to look in, and nothing is found. In order for draw(t)
to compile when t
is of type int
, draw
must be found by ordinary unqualified name lookup, which will only find names that are visible from the definition context ([temp.res.general]/1).