Поведение экземпляра шаблона изменяется при использовании явного квалификатора пространства имен? - PullRequest
7 голосов
/ 10 мая 2019

Я экспериментировал с системой для составных конвейеров, которая включает в себя набор «этапов», которые могут быть шаблонными.Каждый этап обрабатывает свою собственную настройку, выполнение и очистку, а вычитание шаблона используется для построения минимального списка «состояний», используемых конвейером.Это требует довольно большого количества шаблонного кода шаблона, который показал некоторое явно несоответствующее поведение.Несмотря на успешные эксперименты, фактическое внедрение его в нашу кодовую базу привело к ошибкам из-за недопустимых экземпляров.

Потребовалось некоторое время, чтобы отследить разницу между игрушечным (рабочим) решением и более богатой версией, нов конце концов, оно было сужено до явной спецификации пространства имен.

template<typename KeyType = bool>
struct bind_stage
{
    static_assert(!std::is_same<KeyType, bool>::value, "Nope, someone default instantiated me");
};

template<typename BoundStage, typename DefaultStage>
struct test_binding {};

template<template<typename...>class StageTemplate, typename S, typename T>
struct test_binding <StageTemplate<S>, StageTemplate<T>> {};

template<typename T>
auto empty_function(T b) {}

Тогда наш главный:

int main()
{
    auto binder = test_binding<bind_stage<int>, bind_stage<>>();
    //empty_function(binder); // Fails to compile
    ::empty_function(binder); // Compiles happily
    return 0;
}

Теперь я не уверен, ожидаю ли я неудачу или нет.С одной стороны, мы создаем test_binder<bind_stage<int>,bind_stage<bool>>, который, очевидно, включает недопустимую реализацию bind_stage<bool> как часть определения его типа.Который не должен компилироваться.

С другой стороны, он включен исключительно как имя, а не как определение.В этой ситуации это может быть просто объявленный заранее шаблон, и мы ожидаем, что он будет работать до тех пор, пока во внешнем шаблоне на самом деле ничего не ссылается на него.

Чего я не ожидал, так это двух разных вариантов поведения в зависимостио том, добавил ли я (теоретически лишний) глобальный спецификатор пространства имен.

Я пробовал этот код в Visual Studio, Clang и GCC.У всех одинаковое поведение, что заставляет меня отказаться от этой ошибки компилятора.Объясняется ли это поведение чем-то в стандарте C ++?


РЕДАКТИРОВАТЬ: Еще один пример от Даниэля Лэнгра, который для меня менее важен:

template <typename T>
struct X {
    static_assert(sizeof(T) == 1, "Why doesn't this happen in both cases?");
};

template <typename T>
struct Y { };

template <typename T>
void f(T) { }

int main() {
    auto y = Y<X<int>>{};
    // f(y); // triggers static assertion
    ::f(y); // does not
}

Либо X<int> создается в то время какопределение Y<X<int>> или это не так.Какое отношение имеет использование функции в неопределенной области видимости к чему-либо?

1 Ответ

5 голосов
/ 10 мая 2019

Шаблоны создаются при необходимости. Так почему, когда кто-то выполняет неквалифицированный вызов как f(Y<X<int>> {});, компилятор создает экземпляр X<int>, в то время как он этого не делает, когда вызов f квалифицируется как в ::f(X<Y<int>>{})?

Причина: Поиск по имени, зависящий от агента (ADL) (см. [basic.lookup.argdep] ), который имеет место только для неквалифицированных вызовов.

В случае вызова f(Y<X<int>>{}) компилятор должен искать в определении X<int> объявление функции-друга:

template <typename T>
struct X {
    //such function will participate to the overload resolution
    //to determine which function f is called in "f(Y<X<int>>{})"
    friend void f(X&){}
};

ADL, включающий в себя тип аргумента шаблона специализации, который является типом аргумента функции (ой ...), настолько любимый (потому что он почти только вызывает неприятные сюрпризы), что есть предложение удалить его : P0934

...