Почему мои выражения SFINAE больше не работают с GCC 8.2?
Недавно я обновил GCC до 8.2, и большинство моих выражений SFINAE перестали работать.
Следующее несколько упрощено, но демонстрирует проблему:
#include <iostream>
#include <type_traits>
class Class {
public:
template <
typename U,
typename std::enable_if<
std::is_const<typename std::remove_reference<U>::type>::value, int
>::type...
>
void test() {
std::cout << "Constant" << std::endl;
}
template <
typename U,
typename std::enable_if<
!std::is_const<typename std::remove_reference<U>::type>::value, int
>::type...
>
void test() {
std::cout << "Mutable" << std::endl;
}
};
int main() {
Class c;
c.test<int &>();
c.test<int const &>();
return 0;
}
C++ (gcc) - Попробуйте онлайн
C++ (clang) - Попробуйте онлайн
Старые версии GCC (к сожалению, я не помню точной версии, которую я установил ранее), а также Clang компилирует приведенный выше код просто отлично, но GCC 8.2 дает сообщение об ошибке:
: In function 'int main()':
:29:19: error: call of overloaded 'test()' is ambiguous
c.test();
^
:12:10: note: candidate: 'void Class::test() [with U = int&; typename std::enable_if::type>::value>::type ... = {}]'
void test() {
^~~~
:22:10: note: candidate: 'void Class::test() [with U = int&; typename std::enable_if::type>::value)>::type ... = {}]'
void test() {
^~~~
:30:25: error: call of overloaded 'test()' is ambiguous
c.test();
^
:12:10: note: candidate: 'void Class::test() [with U = const int&; typename std::enable_if::type>::value>::type ... = {}]'
void test() {
^~~~
:22:10: note: candidate: 'void Class::test() [with U = const int&; typename std::enable_if::type>::value)>::type ... = {}]'
void test() {
Как обычно бывает, когда разные компиляторы и версии компилятора обрабатывают один и тот же код по-разному, я предполагаю, что я вызываю неопределенное поведение. Что должен сказать стандарт о вышеуказанном коде? Что я делаю не так?
Примечание. Вопрос не в том, как это исправить, есть несколько, которые приходят на ум. Вопрос в том, почему это не работает с GCC 8 - не определено ли оно стандартом или это ошибка компилятора?
Примечание 2: Поскольку все были прыгали по умолчанию void
типа std::enable_if
, я изменил вопрос на использование int
вместо этого. Проблема остается.
Примечание 3: Создан отчет об ошибке GCC
Ответы
Ответ 1
Это мое занятие. Короче говоря, clang прав, и gcc имеет регрессию.
У нас есть [temp.deduct] p7:
Подстановка происходит во всех типах и выражениях, которые используются в типе функции и в объявлениях параметров шаблона. [...]
Это означает, что замена должна произойти, пустой пакет или нет. Поскольку мы все еще находимся в непосредственном контексте, это возможно для SFINAE.
Затем мы имеем, что переменный параметр действительно считается фактическим параметром шаблона; от [temp.variadic] p1
Пакет параметров шаблона является параметром шаблона, который принимает ноль или более аргументов шаблона.
и [temp.param] p2 говорит, какие параметры шаблона непигового типа разрешены:
Параметр шаблона, не относящийся к типу, должен иметь один из следующих (необязательно cv-квалифицированных) типов:
-
тип, который является буквальным, имеет сильное структурное равенство ([class.compare.default]), не имеет изменяемых или изменчивых подобъектов и в котором, если есть оператор-член по умолчанию <=>, он объявляется общедоступным,
-
ссылочный тип lvalue,
-
тип, который содержит тип заполнителя ([dcl.spec.auto]) или
-
заполнитель для выведенного типа класса ([dcl.type.class.deduct]).
Обратите внимание, что void
не соответствует счету, ваш код (как опубликовано) плохо сформирован.
Ответ 2
Я не юрист по языку, но не могу ли следующая цитата каким-то образом связать проблему?
[temp.deduct.type/9]: Если Pi является расширением пакета, тогда образец Pi сравнивается с каждым оставшимся аргументом в списке аргументов шаблона A. Каждое сравнение выводит аргументы шаблона для последующих позиций в пакетах параметров шаблона, расширенных по Пи.
Мне кажется, что, поскольку в списке аргументов шаблона нет оставшегося аргумента, тогда нет сравнения шаблона (который содержит enable_if
). Если нет сравнения, то также нет вычетов и замещений после удержания, которые я считаю. Следовательно, если нет замещения, не применяется SFINAE.
Пожалуйста, поправьте меня, если я ошибаюсь. Я не уверен, применим ли этот конкретный пункт здесь, но есть более похожие правила относительно расширения пакета в [temp.deduct]. Кроме того, это обсуждение может помочь кому-то более опытному решению всей проблемы: https://groups.google.com/a/isocpp.org/forum/#!topic/std-discussion/JwZiV2rrX1A.
Ответ 3
Частичный ответ: используйте typename = typename enable_if<...>, T=0
с разными T
s:
#include <iostream>
#include <type_traits>
class Class {
public:
template <
typename U,
typename = typename std::enable_if_t<
std::is_const<typename std::remove_reference<U>::type>::value
>, int = 0
>
void test() {
std::cout << "Constant" << std::endl;
}
template <
typename U,
typename = typename std::enable_if_t<
!std::is_const<typename std::remove_reference<U>::type>::value
>, char = 0
>
void test() {
std::cout << "Mutable" << std::endl;
}
};
int main() {
Class c;
c.test<int &>();
c.test<int const &>();
return 0;
}
(демо)
Все еще пытаясь понять, что делает std::enable_if<...>::type...
означает, что тип по умолчанию void
.