why no need of forward declaration in static dispatching via templates? - c++

I am playing a bit with static polymorphism, I'm calling a function which internally calls the "right" specialized function depending on the type of the initial argument (basically I'm doing tagging). Here is the code:
#include <iostream>
using namespace std;
// tags
struct tag1{};
struct tag2{};
// the compliant types, all should typedef tag_type
struct my_type1
{
using tag_type = tag1;
};
struct my_type2
{
using tag_type = tag2;
};
// static dispatch via tagging
template <typename T>
void f(T)
{
cout << "In void f<typename T>(T)" << endl;
// why can I call f_helper without forward definition?!?
f_helper(typename T::tag_type{});
}
int main()
{
my_type1 type1;
my_type2 type2;
// how does f below knows about f_helper ?!?!
// even after instantiation f_helper shouldn't be visible!
f(type1);
f(type2);
}
// helper functions
void f_helper(tag1)
{
cout << "f called with my_type1" << endl;
}
void f_helper(tag2)
{
cout << "f called with my_type2" << endl;
}
So, f(T) is called with a parameter my_type1 or my_type2 that internally must typedef tag_type with the appropriate tag tag1/tag2. Depending on this internal tag_type, the "right" wrapper is then called, and this decision is made of course at compile time. Now I really don't understand why this code IS working? Why don't we need to forward-declare f_helper? I first had the wrappers defined before main (and after f), and I though ok, this makes sense, you don't need to forward declare because the compiler instantiate the template only when f(type1); is called (in main()), before it doesn't know the type T, so at the time of instantiation the compiler knows f_wrapper.
But as you see, even if I declare the wrappers AFTER main(), the code still works. Why is this happening? I guess the question is a bit strange, asking why a code works :)
EDIT
The code continues to compile even in gcc5 and gcc HEAD 6.0.0.

f_helper(typename T::tag_type{}) is a type-dependent expression because T::tag_type is a dependent type. This means that f_helper doesn't need to be visible until f<T> is instantiated due to two phase lookup.
EDIT: I'm pretty sure that this is actually undefined behaviour. If we look at 14.6.4.2 [temp.dep.candidate] we see this passage:
For a function call that depends on a template parameter, the
candidate functions are found using the usual lookup rules (3.4.1,
3.4.2, 3.4.3) except that:
— For the part of the lookup using unqualified name lookup (3.4.1) or qualified name lookup (3.4.3), only
function declarations from the template definition context are found.
— For the part of the lookup using associated namespaces (3.4.2), only
function declarations found in either the template definition context
or the template instantiation context are found.
If the function name
is an unqualified-id and the call would be ill-formed or would find a
better match had the lookup within the associated namespaces
considered all the function declarations with external linkage
introduced in those namespaces in all translation units, not just
considering those declarations found in the template definition and
template instantiation contexts, then the program has undefined
behavior.
The last paragraph to me indicates this is undefined behaviour. The function call that depends on a template parameter here is f_helper(typename T::tag_type{}). f_helper isn't visible when f is instantiated, but it would be if we performed name lookup after all translation units have been compiled.

I agree, the code is ill-formed. I'm surprised neither g++ nor clang++ has even a warning about this.
14.6.2/1:
In an expression of the form:
postfix-expression ( expression-list [opt] )
where the postfix-expression is an id-expression, the id-expression denotes a dependent name if any of the expressions in the expression-list is a type-dependent expression (14.6.2.2) or if the unqualified-id of the id-expression is a template-id in which any of the template arguments depends on a template parameter. ... Such names are unbound and are looked up at the point of the template instantiation (14.6.4.1) in both the context of the template definition and the context of the point of instantiation.
[f_helper is a postfix-expression and id-expression, and typename T::tag_type{} is type-dependent, so f_helper is a dependent name.]
14.6.4/1:
In resolving dependent names, names from the following sources are considered:
Declarations that are visible at the point of definition of the template.
Declarations from namespaces associated with the types of the function arguments both from the instantiation context (14.6.4.1) and from the definition context.
14.6.4.1/6:
The instantiation context of an expression that depends on the template arguments is the set of declarations with external linkage declared prior to the point of instantiation of the template specialization in the same translation unit.
14.6.4.2/1:
For a function call that depends on a template parameter, the candidate functions are found using the usual lookup rules (3.4.1, 3.4.2, 3.4.3) except that:
For the part of the lookup using unqualified name lookup (3.4.1) or qualified name lookup (3.4.3), only function declarations from the template definition context are found.
For the part of the lookup using associated namespaces (3.4.2), only function declarations found in either the template definition context or the template instantiation context are found.

The call to f_helper(typename T::tag_type{}); is dependent on the template parameter T, so the name f_helper need not be visible until the point of instantiation of f<T> (due to two phase name lookup).
I believe the code works because implementations are allowed to delay the point of instantiation of function templates until the end of the translation unit, at which time the definitions for f_helper are available.
N3936 §14.6.4.1/8 [temp.point]
A specialization for a function template, a member function template, or of a member function or static data member of a class template may have multiple points of instantiations within a translation unit, and in addition to the points of instantiation described above, for any such specialization that has a point of instantiation within the translation unit, the end of the translation unit is also considered a point of instantiation. A specialization for a class template has at most one point of instantiation within a translation unit. A specialization for any template may have points of instantiation in multiple translation units. If two different points of instantiation give a template specialization different meanings according to the one
definition rule (3.2), the program is ill-formed, no diagnostic required.

Related

Is it a defect in the standard about dependent name resolution for template

About how lookup the dependent name for template, The standard only gives a little sentence like this, there's no more:
In resolving dependent names, names from the following sources are considered:
Declarations that are visible at the point of definition of the template.
Declarations from namespaces associated with the types of the function arguments both from the instantiation context ([temp.point]) and from the definition context.
Consider the below code
struct Test{
using type = int;
};
// #1
template<typename T>
struct TMP{
using type = typename T::type;
};
int main(){
TMP<Test>::type v = 0;
}
For this code, the name type indeed a dependent name because T is a template parameter and here is not a function call, So, the only relevant bullet point is Number 1. It only says the dependent name shall be visible before the template definition, It means in my code, the declaration shall be visible at #1. In actually, typename T::type is a qualified-id, hence qualified name lookup rules apply to it and because T is a template parameter, So the lookup action shall be occurred after given a template argument, namely, during the instantiation of a specialization for such a template. But the quote I cited does not say anything about this. So, I wonder Is it a defect in the standard? If I miss anything that interpret this in the standard, please cite them for this question.
To complete language-lawyer comments, in §"Unknown specializations" of this page
Within a template definition, certain names are deduced to belong to
an unknown specialization, in particular,
a qualified name, if any name that appears to the left of :: is a dependent type that is not a member of the current instantiation
...
In your struct TMP,
the type T is indeed a dependent type
the expression typename T::type is a qualified name and the left if :: is a dependent type
that expression becomes an unknown specialization
Then it is said:
Members of unknown specialization are always dependent, and are looked up and bound at the point of instantiation as all dependent names
which allows the lookup of TMP<Test>::type at the point of instanciation.

Point of instantiation of default arguments in a template function

The standard allows function templates to be instantiated after the enclosing namespace-scope declaration or at the end of the translation unit when they are referred to from a non-template context: [temp.point]/1
For a function template specialization, a member function template
specialization, or a specialization for a member function or static
data member of a class template, if the specialization is implicitly
instantiated because it is referenced from within another template
specialization and the context from which it is referenced depends on
a template parameter, the point of instantiation of the specialization
is the point of instantiation of the enclosing specialization.
Otherwise, the point of instantiation for such a specialization immediately follows the namespace scope declaration or definition that
refers to the specialization.
[temp.point]/8
A specialization for a function template, a member function template,
or of a member function or static data member of a class template may
have multiple points of instantiations within a translation unit, and
in addition to the points of instantiation described above, for any
such specialization that has a point of instantiation within the
translation unit, the end of the translation unit is also considered a
point of instantiation. A specialization for a class template has at
most one point of instantiation within a translation unit. A
specialization for any template may have points of instantiation in
multiple translation units. If two different points of instantiation
give a template specialization different meanings according to the
one-definition rule, the program is ill-formed, no diagnostic
required.
Now consider this minimal reproducible example:
#include <iostream>
#include <array>
struct A {};
std::array<char, 2> show(float, A)
{
std::cout << "2\n";
return {};
}
template<typename T>
struct Fun {
decltype(show(0, T{})) b;
};
template <typename T>
void func(T, int c = sizeof(Fun<T>{}.b))
{
show(0, T{});
std::cout << c << '\n';
}
int main()
{
func(A{});
}
char show(int, A)
{
std::cout << "1\n";
return {};
}
Both GCC and Clang output 1 2 (godbolt).
Here, the instantiation of func<A> (triggered in main) has two points of instantiation: one immediately after main (and thus before the second show) and another at the end of the translation unit. The first 1 indicates that the compilers to instantiate func<A> at the end of the translation unit. However, the default argument sizeof(Fun<T>{}.b) causes Fun<A> to be instantiated, and the second 2 suggests that Fun<A> is instantiated before the second show.
Now, the point of instantiation of default arguments is specified to be that of func<A>: [temp.point]/2
If a function template or member function of a class template is
called in a way which uses the definition of a default argument of
that function template or member function, the point of
instantiation of the default argument is the point of instantiation of
the function template or member function specialization.
Hmm ... This seems to suggest that the two numbers should be the same.
I feel I'm missing something here. Is there any detail that I happened to neglect? Or did I make mistakes?
As quoted in the question [temp.point]/8 says:
If two different points of instantiation give a template specialization different meanings according to the one-definition rule, the program is ill-formed, no diagnostic required.
According to the one-definition rule two definitions are not the same if function call overload resolution of a name used in the definition will yield different entities defined outside the definition. ([basic.def.odr]/6.2)
Overload resolution of the two calls to show in func<A> and in Fun<A> will choose different function overloads depending on whether func<A>'s point of instantiation is immediately after main or at the end of the translation unit, both of which are allowed points of instantiation.
Therefore the program is ill-formed, no diagnostic required.

Why doesn't function declared inside other function participate in argument dependent lookup?

Consider a simple example:
template <class T>
struct tag { };
int main() {
auto foo = [](auto x) -> decltype(bar(x)) { return {}; };
tag<int> bar(tag<int>);
bar(tag<int>{}); // <- compiles OK
foo(tag<int>{}); // 'bar' was not declared in this scope ?!
}
tag<int> bar(tag<int>) { return {}; }
Both [gcc] and [clang] refuses to compile the code. Is this code ill-formed in some way?
foo(tag<int>{}); triggers the implicit instantiation of a specialization of the function call operator member function template of foo's closure type with the template argument tag<int>. This creates a point of instantiation for this member function template specialization. According to [temp.point]/1:
For a function template specialization, a member function template
specialization, or a specialization for a member function or static
data member of a class template, if the specialization is implicitly
instantiated because it is referenced from within another template
specialization and the context from which it is referenced depends on
a template parameter, the point of instantiation of the specialization
is the point of instantiation of the enclosing specialization.
Otherwise, the point of instantiation for such a specialization
immediately follows the namespace scope declaration or definition
that refers to the specialization.
(emphasis mine)
So, the point of instantiation is immediately after main's definition, before the namespace-scope definition of bar.
Name lookup for bar used in decltype(bar(x)) proceeds according to [temp.dep.candidate]/1:
For a function call where the postfix-expression is a dependent
name, the candidate functions are found using the usual lookup rules
(6.4.1, 6.4.2) except that:
(1.1) — For the part of the lookup using unqualified name lookup
(6.4.1), only function declarations from the template definition
context are found.
(1.2) — For the part of the lookup using associated namespaces
(6.4.2), only function declarations found in either the template
definition context or the template instantiation context are found. [...]
Plain unqualified lookup in the definition context doesn't find anything. ADL in the definition context doesn't find anything either. ADL in the instantiation context, according to [temp.point]/7:
The instantiation context of an expression that depends on the
template arguments is the set of declarations with external linkage
declared prior to the point of instantiation of the template
specialization in the same translation unit.
Again, nothing, because bar hasn't been declared at namespace scope yet.
So, the compilers are correct. Moreover, note [temp.point]/8:
A specialization for a function template, a member function template,
or of a member function or static data member of a class template may
have multiple points of instantiations within a translation unit, and
in addition to the points of instantiation described above, for any
such specialization that has a point of instantiation within the
translation unit, the end of the translation unit is also considered
a point of instantiation. A specialization for a class template has
at most one point of instantiation within a translation unit. A
specialization for any template may have points of instantiation in
multiple translation units.
If two different points of instantiation give a template specialization different meanings according to the one-definition rule
(6.2), the program is ill-formed, no diagnostic required.
(emphasis mine)
and the second part of [temp.dep.candidate]/1:
[...] If the call would be ill-formed or would find a better match had
the lookup within the associated namespaces considered all the
function declarations with external linkage introduced in those
namespaces in all translation units, not just considering those
declarations found in the template definition and template
instantiation contexts, then the program has undefined behavior.
So, ill-formed NDR or undefined behavior, take your pick.
Let's consider the example from your comment above:
template <class T>
struct tag { };
auto build() {
auto foo = [](auto x) -> decltype(bar(x)) { return {}; };
return foo;
}
tag<int> bar(tag<int>) { return {}; }
int main() {
auto foo = build();
foo(tag<int>{});
}
Lookup in the definition context still doesn't find anything, but the instantiation context is immediately after main's definition, so ADL in that context finds bar in the global namespace (associated with tag<int>) and the code compiles.
Let's also consider AndyG's example from his comment above:
template <class T>
struct tag { };
//namespace{
//tag<int> bar(tag<int>) { return {}; }
//}
auto build() {
auto foo = [](auto x) -> decltype(bar(x)) { return {}; };
return foo;
}
namespace{
tag<int> bar(tag<int>) { return {}; }
}
int main() {
auto foo = build();
foo(tag<int>{});
}
Again, the instantiation point is immediately after main's definition, so why isn't bar visible? An unnamed namespace definition introduces a using-directive for that namespace in its enclosing namespace (the global namespace in this case). This would make bar visible to plain unqualified lookup, but not to ADL according to [basic.lookup.argdep]/4:
When considering an associated namespace, the lookup is the same as
the lookup performed when the associated namespace is used as a
qualifier (6.4.3.2) except that:
(4.1) — Any using-directives in the associated namespace are
ignored. [...]
Since only the ADL part of the lookup is performed in the instantiation context, bar in the unnamed namespace is not visible.
Commenting out the lower definition and uncommenting the upper one makes bar in the unnamed namespace visible to plain unqualified lookup in the definition context, so the code compiles.
Let's also consider the example from your other comment above:
template <class T>
struct tag { };
int main() {
void bar(int);
auto foo = [](auto x) -> decltype(bar(decltype(x){})) { return {}; };
tag<int> bar(tag<int>);
bar(tag<int>{});
foo(tag<int>{});
}
tag<int> bar(tag<int>) { return {}; }
This is accepted by GCC, but rejected by Clang. While I was initially quite sure that this is a bug in GCC, the answer may actually not be so clear-cut.
The block-scope declaration void bar(int); disables ADL according to [basic.lookup.argdep]/3:
Let X be the lookup set produced by unqualified lookup (6.4.1) and let
Y be the lookup set produced by argument dependent lookup (defined as
follows). If X contains
(3.1) — a declaration of a class member, or
(3.2) — a block-scope function declaration that is not a
using-declaration, or
(3.3) — a declaration that is neither a function nor a function
template
then Y is empty. [...]
(emphasis mine)
Now, the question is whether this disables ADL in both the definition and instantiation contexts, or only in the definition context.
If we consider ADL disabled in both contexts, then:
The block-scope declaration, visible to plain unqualified lookup in the definition context, is the only one visible for all instantiations of the closure type's member function template specializations. Clang's error message, that there's no viable conversion to int, is correct and required - the two quotes above regarding ill-formed NDR and undefined behavior don't apply, since the instantiation context doesn't influence the result of name lookup in this case.
Even if we move bar's namespace-scope definition above main, the code still doesn't compile, for the same reason as above: plain unqualified lookup stops when it finds the block-scope declaration void bar(int); and ADL is not performed.
If we consider ADL disabled only in the definition context, then:
As far as the instantiation context is concerned, we're back to the first example; ADL still can't find the namespace-scope definition of bar. The two quotes above (ill-formed NDR and UB) do apply however, and so we can't blame a compiler for not issuing an error message.
Moving bar's namespace-scope definition above main makes the code well-formed.
This would also mean that ADL in the instantiation context is always performed for dependent names, unless we have somehow determined that the expression is not a function call (which usually involves the definition context...).
Looking at how [temp.dep.candidate]/1 is worded, it seems to say that plain unqualified lookup is performed only in the definition context as a first step, and then ADL is performed according to the rules in [basic.lookup.argdep] in both contexts as a second step. This would imply that the result of plain unqualified lookup influences this second step as a whole, which makes me lean towards the first option.
Also, an even stronger argument in favor of the first option is that performing ADL in the instantiation context when either [basic.lookup.argdep]/3.1 or 3.3 apply in the definition context doesn't seem to make sense.
Still... it may be worth asking about this one on std-discussion.
All quotes are from N4713, the current standard draft.
From unqualified lookup rules ([basic.lookup.unqual]):
For the members of a class X, a name used in a member function body, [...], shall be declared in one of the
following ways
— if X is a local class or is a nested class of a local class, before the definition of class X in a block
enclosing the definition of class X
Your generic lambda is a local class within main, so to use bar, the name bar must appear in a declaration beforehand.

Can't understand name lookup differences between an int and a user defined type - perhaps ADL related

Why does the following code compile:
template<typename T>
void foo(T in) { bar(in); }
struct type{};
void bar(type) {}
int main() { foo(type()); }
When the following does not:
template<typename T>
void foo(T in) { bar(in); }
void bar(int) {}
int main() { foo(42); }
Compiling with GnuC++ 7:
a.cpp: In instantiation of 'void foo(T) [with T = int]':
a.cpp:9:20: required from here
a.cpp:2:21: error: 'bar' was not declared in this scope, and no declarations were found by argument-dependent lookup at the point of instantiation [-fpermissive]
void foo(T in) { bar(in); }
~~~^~~~
a.cpp:8:6: note: 'void bar(int)' declared here, later in the translation unit void bar(int) {}
I would assume that MSVC would compile both (as it does) but that GCC would reject both since GCC/Clang have proper two phase name lookup...
The strange part is not that the int example fails to compile, it is that the type example does since bar is defined after foo. This is due to [temp.dep.candidate] (see third paragraph).
Two-pass compilation of templates
When the compiler parses and compiles a template class or function, it looks up identifiers in two pass:
Template argument independent name lookup: everything that does not depend on the template arguments can be checked. Here, since bar() depends on a template argument, nothing is done. This lookup is done at the point of definition.
Template argument dependent name lookup: everything that could not be looked up in pass #1 is now possible. This lookup is done at the point of instantiation.
You get an error during pass #2.
ADL lookup
When a function name is looked up, it is done within the current context and those of the parameters type. For instance, the following code is valid though f is defined in namespace n:
namespace n { struct type {}; void f(type) {}; }
int main() { n::type t; f(t); } // f is found in ::n because type of t is in ::n
More about ADL (cppreference.com):
Argument-dependent lookup, also known as ADL, or Koenig lookup, is the set of rules for looking up the unqualified function names in function-call expressions, including implicit function calls to overloaded operators. These function names are looked up in the namespaces of their arguments in addition to the scopes and namespaces considered by the usual unqualified name lookup.
Two-pass compilation, ADL lookup and unqualified-id lookup
In your case, those three mechanisms collide. See [temp.dep.candidate]:
For a function call that depends on a template parameter, if the function name is an unqualified-id but not a template-id, the
candidate functions are found using the usual lookup rules (3.4.1,
3.4.2) except that:
— For the part of the lookup using unqualified name lookup (3.4.1), only function declarations with external linkage from the
template definition context are found.
— For the part of the lookup using associated namespaces (3.4.2), only function declarations with external linkage found in either the
template definition context or the template instantiation context are
found.
So, with foo(type()) unqualified-id lookup kicks in and the lookup is done "in either the template definition context or the template instantiation".
With foo(42), 42 being a fundamental type, ADL is not considered and only the "definition context" is considered.
The 1st sample is valid, because ADL takes effect for the name lookup of dependent name in template definition; which makes it possible to find the function bar. (bar(in) depends on the template parameter T.)
(emphasis mine)
For a dependent name used in a template definition, the lookup is postponed until the template arguments are known, at which time ADL examines function declarations that are visible from the template definition context as well as in the template instantiation context, while non-ADL lookup only examines function declarations that are visible from the template definition context (in other words, adding a new function declaration after template definition does not make it visible except via ADL).
And ADL doesn't work with fundamental types, that's why the 2nd sample fails.

Namespace causes sub-optimal template overload resolution

This is very similar to this question, but I'm not sure the answer there is entirely applicable to the minimal code I've put together that demonstrates the issue. (My code does not use trailing-return types, and there are some other differences as well.) Additionally, the issue of whether MSVC's behavior is legal doesn't seem to be addressed.
In short, I'm seeing the compiler select a generic function template instantiation rather than a more-specific overload when the function template is inside a namespace.
Consider the following set of namespace and class definitions:
namespace DoStuffUtilNamespace
{
template<typename UNKNOWN>
void doStuff(UNKNOWN& foo)
{
static_assert(sizeof(UNKNOWN) == -1, "CANNOT USE DEFAULT INSTANTIATION!");
}
}
class UtilForDoingStuff
{
public:
template <typename UNKNOWN>
void doStuffWithObjectRef(UNKNOWN& ref)
{
DoStuffUtilNamespace::doStuff(ref);
}
};
class MyClassThatCanDoStuff { };
namespace DoStuffUtilNamespace
{
using ::MyClassThatCanDoStuff; // No effect.
void doStuff(MyClassThatCanDoStuff& foo) { /* No assertion! */ }
}
... and the following use-cases:
int main()
{
MyClassThatCanDoStuff foo;
DoStuffUtilNamespace::MyClassThatCanDoStuff scoped_foo;
UtilForDoingStuff util;
DoStuffUtilNamespace::doStuff(foo); // Compiles
DoStuffUtilNamespace::doStuff(scoped_foo); // Compiles
util.doStuffWithObjectRef(foo); // Triggers static assert
util.doStuffWithObjectRef(scoped_foo); // Triggers static assert
}
If the entire DoStuffUtilNamespace is eliminated and all its members are moved to global scope, this compiles fine with G++ and Clang++.
With the namespace, doStuff is of course a dependent name. According to the top-voted answer on the similar question, the standard says:
In resolving dependent names, names from the following sources are considered:
Declarations that are visible at the point of definition of the template.
Declarations from namespaces associated with the types of the function arguments both from the instantiation context and from the definition context.
This seems a little odd to me; I don't understand why the first bullet point would specify that the declarations must be visible at the point of definition of the template rather than at the point of instantiation, since the second bullet point explicitly specifies that some declarations visible only at the point of instantiation are allowed. (If someone would like to offer a rationale, I'd appreciate it, but that's not my question because it's my understanding that questions of the form "why did the standards committee decide X" are off topic.)
So I think that explains why util.doStuffWithObjectRef(foo); triggers the static assertion: doStuff(MyClassThatCanDoStuff&) hasn't been declared at the point of definition of UtilForDoingStuff::doStuffWithObjectRef<UNKNOWN>(UNKNOWN&). And indeed moving the class UtilForDoingStuff definition after the doStuff overload has been defined seems to fix the issue.
But what exactly does the standard mean by "namespaces associated with the types of the function arguments"? Shouldn't the using ::MyClassThatCanDoStuff declaration, together with the explicit scoping of the scoped_foo instance type within the namespace, trigger argument-dependent lookup, and shouldn't this look-up find the non-asserting definition of doStuff()?
Also, the entire code is compiled without error using clang++ -ftemplate-delayed-parsing, which emulates MSVC's template-parsing behavior. This seems preferable, at least in this particular case, because the ability to add new declarations to a namespace at any time is one of the primary appeals of namespaces. But, as noted above, it doesn't quite seem to follow the letter of the law, according to the standard. Is it permissible, or is it an instance of non-conformance?
EDIT:: As pointed out by KIIV, there is a workaround; the code compiles if template specialization is used instead of overloading. I would still like to know the answers to my questions about the standard.
With the namespace, doStuff is of course a dependent name.
You are starting from the wrong premise. There is no ADL for a qualified call like DoStuffUtilNamespace::doStuff(ref). [basic.lookup.argdep]/p1, emphasis mine:
When the postfix-expression in a function call (5.2.2) is an
unqualified-id, other namespaces not considered during the usual
unqualified lookup (3.4.1) may be searched, and in those namespaces,
namespace-scope friend function or function template declarations
(11.3) not otherwise visible may be found.
DoStuffUtilNamespace::doStuff is a qualified-id, not an unqualified-id. ADL doesn't apply.
For this reason, DoStuffUtilNamespace::doStuff is also not a dependent name. [temp.dep]/p1:
In an expression of the form:
postfix-expression ( expression-listopt)
where the postfix-expression is an unqualified-id, the
unqualified-id denotes a dependent name if [...]. If an operand of an operator is a type-dependent expression, the operator also denotes
a dependent name. Such names are unbound and are looked up at the
point of the template instantiation (14.6.4.1) in both the context of
the template definition and the context of the point of instantiation
(The italicization of dependent name indicate that this paragraph is defining the term.)
Instead, per [temp.nondep]/p1:
Non-dependent names used in a template definition are found using the
usual name lookup and bound at the point they are used.
which doesn't find your later overload declaration.
Specialization works because it's still the same function template declaration that's used; you just supplied a different implementation than the default one.
But what exactly does the standard mean by "namespaces associated with
the types of the function arguments"? Shouldn't the using ::MyClassThatCanDoStuff declaration, together
with the explicit scoping of the scoped_foo instance type within the
namespace, trigger argument-dependent lookup
No. using-declarations do not affect ADL. [basic.lookup.argdep]/p2, emphasis mine:
For each argument type T in the function call, there is a set of
zero or more associated namespaces and a set of zero or more
associated classes to be considered. The sets of namespaces and
classes is determined entirely by the types of the function arguments
(and the namespace of any template template argument).
Typedef names and using-declarations used to specify the types do not contribute to this set. The sets of namespaces and classes are
determined in the following way:
If T is a fundamental type, [...]
If T is a class type (including unions), its associated classes are: the class itself; the class of which it is a member, if any; and its
direct and indirect base classes. Its associated namespaces are the
innermost enclosing namespaces of its associated classes. Furthermore,
if T is a class template specialization, its associated namespaces and
classes also include: the namespaces and classes associated with the
types of the template arguments provided for template type parameters
(excluding template template parameters); the namespaces of which any
template template arguments are members; and the classes of which any
member templates used as template template arguments are members. [
Note: Non-type template arguments do not contribute to the set of associated namespaces. —end note ]
[...]
With template specialization I can get it work:
namespace DoStuffUtilNamespace
{
template<typename UNKNOWN>
void doStuff(UNKNOWN& foo)
{
static_assert(sizeof(UNKNOWN) == -1, "CANNOT USE DEFAULT INSTANTIATION!");
}
}
class UtilForDoingStuff
{
public:
template <typename UNKNOWN>
void doStuffWithObjectRef(UNKNOWN& ref)
{
DoStuffUtilNamespace::doStuff(ref);
}
};
class MyClassThatCanDoStuff { };
namespace DoStuffUtilNamespace
{
using ::MyClassThatCanDoStuff;
template <> void doStuff<MyClassThatCanDoStuff>(MyClassThatCanDoStuff& foo) { /* No assertion! */ }
}
int main()
{
MyClassThatCanDoStuff foo;
DoStuffUtilNamespace::MyClassThatCanDoStuff scoped_foo;
UtilForDoingStuff util;
DoStuffUtilNamespace::doStuff(foo); // Compiles
DoStuffUtilNamespace::doStuff(scoped_foo); // Compiles
util.doStuffWithObjectRef(foo); // Compiles
util.doStuffWithObjectRef(scoped_foo); // Compiles
}
Declarations from namespaces associated with the types of the function arguments both from the instantiation context and from the definition context.
Example with the following code which prints B::foo Demo
namespace A
{
template <typename T>
void foo(const T&) {std::cout << "A::foo" << std::endl;}
template <typename T>
void bar(const T& t) {
foo(t); // thank to ADL, it will also look at B::foo for B::S.
}
}
namespace B
{
struct S {};
void foo(const S&) {std::cout << "B::foo" << std::endl;}
}
int main()
{
B::S s;
A::bar(s);
}
So when calling ?::foo(const B::S&), the second bullet point adds B::foo to the list of overloads.
why template-specialization works in this case
There is only one function:
template<>
void DoStuffUtilNamespace::doStuff<MyClassThatCanDoStuff>(MyClassThatCanDoStuff& foo);
even if it is defined later.
Note that the fact that there is a specialization should be known in the translation unit, else the program is ill formed (doesn't respect ODR).
while overloading doesn't.
You think:
So I think that explains why util.doStuffWithObjectRef(foo); triggers the static assertion: doStuff(MyClassThatCanDoStuff&) hasn't been declared at the point of definition of UtilForDoingStuff::doStuffWithObjectRef<UNKNOWN>(UNKNOWN&). And indeed moving the class UtilForDoingStuff definition after the doStuff overload has been defined seems to fix the issue.
Exactly.