Это очень похоже на этот вопрос, но я не уверен, что ответ полностью применим к минимальному коду, который я ve собрал, что демонстрирует проблему. (В моем коде не используются возвращаемые типы, а также есть некоторые другие отличия.) Кроме того, проблема законности поведения MSVC, по-видимому, не решена.
Короче говоря, я вижу, что компилятор выбирает экземпляр общего шаблона функции, а не более конкретную перегрузку, когда шаблон функции находится внутри пространства имен.
Рассмотрим следующий набор определений пространств имен и классов:
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! */ }
}
... и следующие варианты использования:
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
}
Если весь DoStuffUtilNamespace
удаляется, а все его элементы перемещаются в глобальную область, это прекрасно компилируется с G++ и Clang++.
С пространством имен doStuff
, конечно же, является зависимым именем. Согласно ответу с наибольшим количеством голосов на аналогичный вопрос, стандарт гласит:
При разрешении зависимых имен учитываются имена из следующих источников:
Объявления, видимые в точке определения шаблона.
Объявления из пространств имен, связанных с типами аргументов функции, как из контекста инстанцирования, так и из контекста определения.
Мне это кажется немного странным; Я не понимаю, почему в первом пункте указывается, что объявления должны быть видны в точке определения шаблона, а не в точке создания экземпляра, поскольку второй пункт явно указывает, что разрешены некоторые объявления, видимые только в точке инстанцирования, разрешены. (Если кто-то захочет предложить обоснование, я был бы признателен, но это не мой вопрос, потому что я понимаю, что вопросы в форме, почему комитет по стандартам решил X, не по теме.)
Итак, я думаю, это объясняет, почему util.doStuffWithObjectRef(foo);
запускает статическое утверждение: doStuff(MyClassThatCanDoStuff&)
не было объявлено в точке определения UtilForDoingStuff::doStuffWithObjectRef<UNKNOWN>(UNKNOWN&)
. И действительно, перемещение определения class UtilForDoingStuff
после определения перегрузки doStuff
, кажется, решает проблему.
Но что именно подразумевает стандарт под пространствами имен, связанными с типами аргументов функции? Не должно ли объявление using ::MyClassThatCanDoStuff
вместе с явным определением области действия типа экземпляра scoped_foo
в пространстве имен инициировать поиск, зависящий от аргумента, и не должен ли этот поиск найти неутверждающее определение doStuff()
?
Кроме того, весь код компилируется без ошибок с использованием clang++ -ftemplate-delayed-parsing
, который имитирует поведение MSVC при синтаксическом анализе шаблонов. Это кажется предпочтительным, по крайней мере, в данном конкретном случае, потому что возможность добавлять новые объявления в пространство имен в любое время является одним из основных преимуществ пространств имен. Но, как отмечалось выше, согласно стандарту, это не совсем соответствует букве закона. Допустимо ли это или это случай несоответствия?
EDIT:: Как указал KIIV, есть обходной путь; код компилируется, если вместо перегрузки используется специализация шаблона. Хотелось бы еще узнать ответы на свои вопросы по стандарту.