Почему мои выражения 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.