Путаница с жесткой ошибкой в SFINAE
Что касается следующего кода (https://wandbox.org/permlink/nhx4pheijpTF1ohf, воспроизведенного ниже для удобства)
#include <type_traits>
#include <utility>
namespace foo_name {
template <typename T>
void foo();
template <>
void foo<int>();
template <typename T>
struct c_size;
template <>
struct c_size<int> : public std::integral_constant<int, 1> {};
} // namespace foo_name
template <typename Type>
class Foo {
public:
template <typename T>
static decltype(auto) impl(T&& t) {
using foo_name::foo;
return foo(std::forward<T>(t));
}
};
class Something {};
template <typename Type, typename T = std::decay_t<Type>>
using EnableIfHasFoo = std::void_t<
decltype(Foo<T>::impl(std::declval<T>())),
decltype(foo_name::c_size<Type>::value)>;
template <typename Type, typename = std::void_t<>>
class Test {};
template <typename Type>
class Test<Type, EnableIfHasFoo<Type>> {};
int main() {
static_cast<void>(Test<Something>{});
}
Вышеприведенный код выходит с ошибкой, потому что экземпляр Foo<T>::impl()
вызывает жесткую ошибку и не может использоваться в контексте SFINAE. Но странно, что когда вы переключаете порядок вещей в void_t
в EnableIfHasFoo
(на следующий https://wandbox.org/permlink/at1KkeCraNwHGmUI), он будет компилировать
template <typename Type, typename T = std::decay_t<Type>>
using EnableIfHasFoo = std::void_t<
decltype(foo_name::c_size<Type>::value),
decltype(Foo<T>::impl(std::declval<T>()))>;
Теперь вопросы
- Почему код изначально не компилируется? Создание экземпляра
Foo<T>::impl()
находится в контексте замены, поэтому оно должно работать?
- Подставляя
foo_name::foo(T)
вместо первого аргумента в void_t
, он скомпилирует его (см. https://wandbox.org/permlink/g3NaPFZxdUPBS7oj), почему? Как добавление одного дополнительного слоя косвенности делает ситуацию другой?
- Почему порядок в
void_t
имеет значение, компилятор замыкает выражения в пакете типов?
Ответы
Ответ 1
1) и 2) имеют тот же ответ; SFINAE не работает с возвратом типа вывода, поскольку тело функции не в ближайшем контексте:
10 - Вывод типа возвращаемого значения для шаблона функции с заполнителем в его объявленном типе возникает, когда определение создается, даже если тело функции содержит оператор return
с не-зависимым от типа операнд. [Примечание: поэтому любое использование специализации шаблона функции вызовет неявное конкретизации. Любые ошибки, возникающие из этого экземпляра, не находятся в непосредственном контексте функции типа и может привести к плохому формированию программы (17.8.2). - конечная нота]
3) более интересный вопрос; короткое замыкание является преднамеренным и гарантируется [temp.deduct]:
7 - [...] Замена продолжается в лексическом порядке и останавливается, когда встречается условие, вызывающее сбой вывода.
Это короткое замыкание работает для gcc, clang и ICC, но, к сожалению, MSVC (как CL 19 2017 RTW) ошибочно, например:
template<class T> auto f(T t) -> decltype(t.spork) { return t.spork; }
template<class T> auto g(T t) { return t.spork; }
int x(...);
template<class...> using V = void;
template<class T> auto x(T t) -> V<decltype(f(t)), decltype(g(t))> {}
int a = x(0);