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

Как это обычно бывает, когда разные компиляторы и версии компиляторов обрабатывают один и тот же код по-разному, я предполагаю, что вызываю поведение undefined. Что в стандарте говорится о приведенном выше коде? Что я делаю не так?


Примечание: Вопрос не в том, как это исправить, на ум приходят несколько. Вопрос в том, что Зачем не работает с GCC 8 - это не определено стандартом или это ошибка компилятора?

Заметка 2: Поскольку все прыгали на типе void по умолчанию для std::enable_if, я изменил вопрос, чтобы вместо этого использовать int. Проблема остается.

Заметка 3:Отчет об ошибке GCC создан

Судя по Godbolt, он работал до gcc 7.3 (и вы можете видеть в сборке, что он работает правильно).

Max Langhof 10.08.2018 15:42

что вы привязываете для расширения с ... после ::type?

Tyker 10.08.2018 15:46

Что здесь означает многоточие? Если условие истинно, то фактически имеется template <typename U, void...>.

Daniel Langr 10.08.2018 15:46

Хороший момент, это пережиток упрощения кода. Замена стандартного void на int вызывает тот же эффект. Исходный код использует перечисление без членов, чтобы предотвратить случайное указание второго параметра шаблона.

zennehoy 10.08.2018 15:49

Как насчет удаления многоточия?

Slava 10.08.2018 15:54

Удаление многоточия, замена void по умолчанию на int и добавление значения по умолчанию решает проблему, да. Реальный вопрос: что такого в приведенном выше коде, который раньше работал нормально, но больше не работает с GCC 8?

zennehoy 10.08.2018 15:56

Думаю, более упрощенный пример той же проблемы: wandbox.org/permlink/xFX6AzuqMB7GSa87.

Daniel Langr 10.08.2018 16:17

@DanielLangr Этот не работает в версиях gcc, прямо противоположных той, что указана в вопросе ...

Max Langhof 10.08.2018 16:21

@MaxLanghof Конечно, но причина должна быть та же.

Daniel Langr 10.08.2018 16:23

@xskxzr Судя по этому вопросу, void... разрешен только для пустых пакетов параметров, что здесь и требуется. Даже если это было незаконно, замена void по умолчанию на int в std::enable_if ничего не меняет в вопросе.

zennehoy 10.08.2018 16:28

Минимальный пример: godbolt.org/g/P9z1pt gcc7.1 OK gcc 8.x KO

YSC 10.08.2018 16:56

смешно, без второго определения: godbolt.org/g/RgwSVJ gcc7.1 KO, gcc 8.x OK

YSC 10.08.2018 17:00

@YSC Это потому, что f во втором примере существует только для постоянного параметра шаблона.

zennehoy 10.08.2018 17:03

Это странно: godbolt.org/g/mnWcg6 gcc7.1 OK, gcc 8.x OK

YSC 10.08.2018 17:05

@zennehoy Да, но f<int> называется!

YSC 10.08.2018 17:05

Это действительно похоже на ошибку gcc.

YSC 10.08.2018 17:06

@YSC Не совсем так, попытка вызвать f<int> приводит к ошибке, поскольку f существует только, например, для f<const int>. Кроме того, пакет параметров шаблона void..., не относящийся к типу, не является проблемой - смело заменяйте тип std::enable_if с void на, например, int.

zennehoy 10.08.2018 17:08

@zennehoy это, что странно: на gcc 8.1 f<int>() работает успешно. Смотрите: godbolt.org/g/RgwSVJ

YSC 10.08.2018 17:24

@YSC Ах да, извините, я смотрел только на колонку 7.1. Что-то тут определенно странное ... (Clang тоже выдает ошибку, как я и ожидал).

zennehoy 10.08.2018 17:30

Об этом конкретном моменте я задал вопрос: stackoverflow.com/q/51789825/5470596

YSC 10.08.2018 17:35

Эмм ... Я думаю, что эта почта - более подходящий дубликат, хотя новые версии компиляторов меняют свое поведение, интересно ...

xskxzr 10.08.2018 17:57
22
22
1 578
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Частичный ответ: используйте typename = typename enable_if<...>, T=0 с разными T:

#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.

См. stackoverflow.com/a/23711944/694509, в основном он требует, чтобы пакет параметров был пустым. Не стесняйтесь добавлять int, чтобы заменить тип void по умолчанию - это ничего не меняет в вопросе.

zennehoy 10.08.2018 16:48

@zennehoy Примечание: без добавленного параметра tparam (int и char) ваши две функции шаблона имеют одинаковую сигнатуру шаблона, отсюда и двусмысленность. Я не понимаю, почему это раньше работало.

YSC 10.08.2018 16:51

Как же так? Один из них - template <typename U, void...>, а другой - SFINAE, то есть template <typename U, [error: std::enable_if has no member type]...>.

zennehoy 10.08.2018 16:59

@YSC: typename = std::enable_if_t<cond> требует "странного" дополнительного фиктивного параметра, лучше использовать std::enable_if_t<cond, int> = 0.

Jarod42 10.08.2018 18:37

Я не языковой юрист, но разве следующая цитата не может быть как-то связана с проблемой?

[temp.deduct.type/9]: If Pi is a pack expansion, then the pattern of Pi is compared with each remaining argument in the template argument list of A. Each comparison deduces template arguments for subsequent positions in the template parameter packs expanded by Pi.

Мне кажется, что раз нет оставшийся аргумент в списке аргументов шаблона, то нет и сравнения шаблон (который содержит enable_if). Если нет сравнения, то, как мне кажется, нет и дедукции, и замена происходит после дедукции. Следовательно, если нет замены, SFINAE не применяется.

Пожалуйста, поправьте меня, если я ошибаюсь. Я не уверен, применим ли здесь этот конкретный параграф, но в [temp.deduct] есть более похожие правила относительно расширения пакетов. Кроме того, это обсуждение может помочь более опытному человеку решить всю проблему: https://groups.google.com/a/isocpp.org/forum/#!topic/std-discussion/JwZiV2rrX1A.

Интересно, что это обсуждение привело к выводу, что это была ошибка в Clang, которая впоследствии была исправлена! Кажется, в стандарте есть некоторая неопределенность по этому поводу ...

zennehoy 10.08.2018 17:33
Ответ принят как подходящий

Это мой взгляд на это. Короче говоря, clang прав, а у gcc есть регресс.

У нас согласно [temp.deduct] p7:

The substitution occurs in all types and expressions that are used in the function type and in template parameter declarations. [...]

Это означает, что замена должна происходить независимо от того, пуста ли пачка или нет. Поскольку мы все еще находимся в непосредственном контексте, это возможно для SFINAE.

Далее мы видим, что вариативный параметр действительно считается фактическим параметром шаблона; из [temp.variadic] p1

A template parameter pack is a template parameter that accepts zero or more template arguments.

а [temp.param] p2 говорит, какие параметры шаблона, не относящиеся к типу, разрешены:

A non-type template-parameter shall have one of the following (optionally cv-qualified) types:

  • a type that is literal, has strong structural equality ([class.compare.default]), has no mutable or volatile subobjects, and in which if there is a defaulted member operator<=>, then it is declared public,

  • an lvalue reference type,

  • a type that contains a placeholder type ([dcl.spec.auto]), or

  • a placeholder for a deduced class type ([dcl.type.class.deduct]).

Обратите внимание, что void не соответствует всем требованиям, ваш код (опубликованный) неверен.

Проблема не в типе void по умолчанию для std::enable_if! Если я правильно понимаю ваш ответ, SFINAE должна нормально работать с другим типом (например, int). Этого нет в GCC 8.2.

zennehoy 10.08.2018 20:19

@zennehoy Вот почему я сказал во втором предложении «регресс, если не void» :)

Rakete1111 10.08.2018 20:19

Вы не против сократить свой ответ на этот вопрос? Я указал int в качестве типа для std::enable_if в вопросе сейчас, поскольку я действительно не собирался начинать обсуждение void..., который изначально был пережитком моего упрощения кода. Обратите внимание, что другие приходят к другому выводу, чем вы, кстати: stackoverflow.com/a/23711944/694509 :)

zennehoy 10.08.2018 20:34

@zennehoy Я не совсем понимаю ответ; Спасибо хоть

Rakete1111 10.08.2018 20:38

@zenn этот другой ответ неверен, так как это не расширение пакета. Это просто нерасширяемый пакет параметров шаблона, который, если бы вы дали ему имя, можно было бы расширить в другом месте.

Johannes Schaub - litb 10.08.2018 21:06

Другие вопросы по теме