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 ]
[...]