Тип возврата условного оператора и двухэтапный поиск

Рассмотрим следующий фрагмент:

struct Base { };
struct Derived : Base { };

void f(Base &) { std::cout << "f(Base&)\n"; }

template <class T = int>
void g() {
    Derived d;
    f(T{} ? d : d); // 1
}

void f(Derived &) { std::cout << "f(Derived&)\n"; }

int main() {
    g();
}

В этом случае я считаю, что вызов функции f в // 1 следует искать на первом этапе, поскольку тип его аргумента однозначно Derived&, и, таким образом, разрешаться в f(Base&), который является единственным в области видимости.

Clang 3.8.0 со мной согласен, но GCC 6.1.0 этого не делает и откладывает поиск f до второй фазы, где будет найден f(Derived&).

Какой компилятор прав?


person Quentin    schedule 01.06.2016    source источник
comment
Подал это как основную проблему.   -  person Columbo    schedule 01.06.2016
comment
@Columbo Я склонен думать, что в данном случае правила работают нормально. Я бы не хотел, чтобы поиск имени для f внезапно изменился и включил больше кандидатов, если я изменю условное выражение на T{} ? d : e в какой-то момент в будущем. Особенно, если e является ссылкой, которая инициализируется с помощью d, но может измениться, чтобы ссылаться на что-то другое в будущем. Кроме того, возможность форсировать зависимое разрешение имени для вызова функции с использованием чего-то вроде f((always_void<T>() , arg)), даже если arg не является зависимым, кажется мне скорее функцией, чем ошибкой.   -  person bogdan    schedule 01.06.2016
comment
@bogdan, учитывая, что условный оператор уже имеет свои собственные правила для вывода типа своего результата и что тип условия не мешает, я не вижу ничего удивительного. Первый операнд можно просто проигнорировать, чтобы определить, является ли выражение зависимым. С другой стороны, тип результата оператора запятой зависит от обоих операндов, поэтому такой ярлык невозможен, поэтому ваш пример все равно будет работать нормально (что, я согласен, очень полезная функция).   -  person Quentin    schedule 01.06.2016
comment
@Quentin А, значит, вы думаете, что условное выражение следует выделить, потому что оно не может быть перегружено. Это хороший момент. (Сначала у меня было другое впечатление, извините.) Одна потенциальная проблема, о которой я могу думать, заключается в том, что создание такого условного выражения, не зависящего от типа, сделало бы такие вещи, как noexcept(T{} ? d : d), не зависящими от значения, что было бы неправильно (конструктор T может throw), поэтому необходимо добавить некоторые дополнительные особые случаи. Не уверен, что оно того стоит, но теперь я думаю, что стоит задуматься. Чем больше я думаю об этом, тем интереснее становится твой вопрос!   -  person bogdan    schedule 01.06.2016
comment
@bogdan Это не относится к условному оператору. noexcept(typeid(T{})) также не зависит от значения.   -  person Columbo    schedule 01.06.2016
comment
@Columbo :-) throw просто не вырезал, не так ли? Хорошая находка, хотя в вашем конкретном примере правильность не затрагивается (T{} всегда является значением prvalue, поэтому оно не оценивается и typeid не может выбрасывать), тогда как для условного это было бы. Однако для template<class T> void f(T& t) { constexpr bool n = noexcept(typeid(t)); } у нас есть проблема; Я думаю, что вы только что нашли здесь незаметную стандартную ошибку. И Clang, и GCC рассматривают его как зависящее от значения, хотя GCC неправильно устанавливает его на true во всех случаях.   -  person bogdan    schedule 01.06.2016
comment
@bogdan Да, я имел в виду это (извините, не обратил особого внимания на детали). Тем не менее, я также хочу указать, что элемент неожиданности не является хорошим аргументом IMO, поскольку двухфазный поиск обычно противоречит интуиции (и критикуется по этой причине), поэтому изменение аргумента в вызове зависимой функции никогда не должно давать неожиданности (т. е. я сам по себе ожидал бы какого-то перерыва, возможно).   -  person Columbo    schedule 02.06.2016
comment
@Коломбо Правда. Я на пороге этого, все еще склоняюсь к своей первой реакции (оставьте все как есть). Есть еще пара вещей, которые, как мне кажется, стоит сказать по этому поводу, поэтому я создал чат и добавил туда еще несколько комментариев.   -  person bogdan    schedule 03.06.2016
comment
@Columbo: Какой номер выпуска? Я не могу найти это.   -  person Davis Herring    schedule 17.08.2019


Ответы (3)


Используя последнюю версию Стандарт C++ В настоящее время n4582.

В разделе 14.6 (стр. 10) говорится, что имя связано в точке объявления, если имя не зависит от параметра шаблона. Если это зависит от параметра шаблона, это определяется в разделе 14.6.2.

Далее в разделе 14.6.2.2 говорится, что выражение зависит от типа, если любое подвыражение зависит от типа.

Теперь, поскольку вызов f() зависит от его параметра. Вы смотрите на тип параметра, чтобы увидеть, зависит ли он от типа. Параметр False<T>::value ? d : d. Здесь первое условное выражение зависит от типа T.

Поэтому мы заключаем, что вызов связан с точкой создания экземпляра, а не с объявлением. И поэтому должен привязываться к: void f(Derived &) { std::cout << "f(Derived&)\n"; }

Таким образом, g++ имеет более точную реализацию.

14.6 Разрешение имени [temp.res]

Пункт 10:

Если имя не зависит от параметра-шаблона (как определено в 14.6.2), объявление (или набор объявлений) для этого имени должно находиться в области видимости в точке, где имя появляется в определение шаблона; имя привязано к объявлению (или объявлениям), найденным в этой точке, и на эту привязку не влияют объявления, видимые в момент создания экземпляра.

14.6.2.2 Выражения, зависящие от типа [temp.dep.expr]

За исключением случаев, описанных ниже, выражение зависит от типа, если любое подвыражение зависит от типа.

person Martin York    schedule 01.06.2016
comment
Что ж, это позволило бы выставить перегрузку тройного оператора, не вызывая перерывов в сборке (поскольку существование такого оператора означало бы, что тип a?b:c может фактически зависеть от типа a, а не просто зависит в стандарте) . - person Yakk - Adam Nevraumont; 01.06.2016
comment
@Yakk: в стандарте не говорится, что тип зависит от a, который, очевидно, определяется самим тройным оператором. В нем говорится, что это зависимое от типа выражение (которое используется только для вывода параметров), которое определяет, в какой точке компиляции связаны имена. - person Martin York; 01.06.2016
comment
Да. Я просто заметил, что эта причуда формальной зависимости от типа, которая не согласуется с фактической зависимостью типа, означает, что если бы тройная перегрузка когда-либо была добавлена ​​​​в C++, им не пришлось бы добавлять здесь зависимость от типа. Например, достаточно слабый аргумент, почему эта кажущаяся неточность или ошибка в стандарте не может быть совершенно глупой. - person Yakk - Adam Nevraumont; 01.06.2016

Я думаю, что gcc (и, кстати, Visual Studio) правы в этом вопросе.

n4582, §14.6.2.2

За исключением случаев, описанных ниже, выражение зависит от типа, если любое подвыражение зависит от типа.

В T{} ? d : d есть 3 подвыражения:

  • T{}, очевидно, зависит от типа
  • d (2 раза), не зависит от типа

Поскольку существует подвыражение, зависящее от типа, а тернарный оператор не фигурирует в списке исключений в §14.6.2.2, он считается зависимым от типа.

person Synxis    schedule 01.06.2016
comment
Нет особого смысла смотреть на MSVC при работе с двухэтапным поиском - он вообще его не реализует. - person T.C.; 01.06.2016

Согласно проекту С++ (n4582) §14.7.1.5:

Если специализация шаблона функции не была создана явно или явно специализирована, специализация шаблона функции создается неявно, когда на специализацию ссылаются в контексте, который требует существования определения функции. Если вызов не является явной специализацией шаблона функции или функцией-членом явно специализированного шаблона класса, аргумент по умолчанию для шаблона функции или функции-члена шаблона класса создается неявно, когда функция вызывается в контексте, который требует значение аргумента по умолчанию.

Я бы сказал, что gcc более прав в этом отношении.

Если вы, например, создаете специализированную версию void g(), вы получаете оба компилятора делают то же самое.

person user1810087    schedule 01.06.2016