C(int x, int y){}
template<typename = std::enable_if_t<T>>
explicit C(int x) {}
};
int main(){
C<int> a(2);
return 0;
}
почему такое компилится msvc и не компилится gcc и клангом. Где баг где фича?
По идее не должно компилиться, потому что substitution тут нет, но msvc компилит
Упрощу пример, убрав всё лишнее: template <bool T> class C { public: template<typename = std::enable_if_t<!T>> void foo(){} }; // ... C<true> a{}; По сути, когда мы инстанцируем C, мы получаем эквивалент следующего псевдокода: template <> class C<true> { public: template<typename = std::enable_if_t<!true>> void foo(){} }; Так что да, enable_if_t всегда содержит ложное условие для текущей специализации C, что равнозначно написанию std::enable_if_t<false> в любом нешаблонном контексте — это всегда ошибка компиляции и SFINAE не спасает потому что тут нет SFINAE-контекста. GCC и Clang, кажется правы Могу лишь предположить, что MSVC откладывает момент инстанцирования дефолтного параметра функции до инстанцирования самого шаблона функции
Интересно, что констрейнты справляются: template <bool B> struct C { void foo() requires (!B) {} }; int main() { C<true> a; } При этом вызов a.foo() будет ошибкой. Я попытался придумать как воспроизвести исключение метода из класса без requires и такое чувство, что никак. Приходят в голову методы-уродцы исключить через CRTP (наследуясь от базового класса в котором есть или нет и т.п.), но если поставить условие обойтись без наследования, то интересно есть ли метод воспроизвести шаблонной магией то, что легко делают констрейнты?
template <bool B> struct X { template <bool B_ = B, [sfinae on B_]> void foo(); }; Должно решить проблему
>> Интересно, что констрейнты справляются Да, если не ошибаюсь, это намеренно заложенное в них поведение, чтобы упростить включение/выключение функций в зависимости от параметров класса (в том числе деструктора)
Да, логично, спасибо. Я чего-то не подумал. Просто распространяем SFINAE вниз. И так же чинится оригинальный кейс от @scaredpepe https://godbolt.org/z/3Yro8GnYd
без этого зачем вообще они нужны были бы?)
Довольно странное заявление при том, что они: 1) значительно упрощают код; 2) зависимы между собой и позволяют "перегрузки"; 3) лучше понятны компилятору, что потенциально упрощает и ускоряет диагностику ошибок компилятором
Я бы добавил ещё один интересный хак: template <bool B> struct X { template <typename..., bool B_ = B, [sfinae on B_]> void foo(); }; Это не позволит случайно переопределить дефолтный параметр извне. По желанию добавить ограничение ещё и на то, чтобы пак был пуст
Да, только получается MSVC вообще не инстанцирует его
Обсуждают сегодня