Я пытаюсь понять, почему кусок шаблона метапрограммирования - это , а не, генерирующий бесконечную рекурсию. Я попытался как можно больше уменьшить тестовый пример, но все еще есть немного настройки, поэтому несите меня :)Ожидается создание бесконечного рекурсивного шаблона?
Настройка следующая. У меня есть общие функции foo(T)
который делегирует осуществление к родовому функтора под названием foo_impl
через его оператор вызова, например:
template <typename T, typename = void>
struct foo_impl {};
template <typename T>
inline auto foo(T x) -> decltype(foo_impl<T>{}(x))
{
return foo_impl<T>{}(x);
}
foo()
использует decltype отставая тип возвращаемого значения для SFINAE целей. Реализация по умолчанию foo_impl
не определяет оператор вызова. Далее, у меня есть тип-признак, который определяет, может ли foo()
быть вызвана с аргументом типа T
:
template <typename T>
struct has_foo
{
struct yes {};
struct no {};
template <typename T1>
static auto test(T1 x) -> decltype(foo(x),void(),yes{});
static no test(...);
static const bool value = std::is_same<yes,decltype(test(std::declval<T>()))>::value;
};
Это просто классическая реализация типа признака с помощью выражения SFINAE: has_foo<T>::value
будет справедливо, если действующий номер foo_impl
специализация есть для T
, false в противном случае. Наконец, у меня есть два специализаций функтора реализации для целочисленных типов и для типов с плавающей точкой:
template <typename T>
struct foo_impl<T,typename std::enable_if<std::is_integral<T>::value>::type>
{
void operator()(T) {}
};
template <typename T>
struct foo_impl<T,typename std::enable_if<has_foo<unsigned>::value && std::is_floating_point<T>::value>::type>
{
void operator()(T) {}
};
В последней foo_impl
специализации, один для типов с плавающей точкой, я добавил дополнительное условие, что foo()
должны быть доступны для типа unsigned
(has_foo<unsigned>::value
).
То, что я не понимаю, почему составители (GCC & лязг оба) принимает следующий код:
int main()
{
foo(1.23);
}
В моем понимании, когда foo(1.23)
называется следующее должно произойти:
- специализация
foo_impl
для интегральных типов отбрасывается, поскольку1.23
не является неотъемлемой частью, поэтому рассматривается только вторая специализацияfoo_impl
; - условие включения второй специализации
foo_impl
содержитhas_foo<unsigned>::value
, то есть компилятор должен проверить, можно ли вызыватьfoo()
по типуunsigned
; - для того, чтобы узнать, может ли
foo()
назвать типunsigned
, компилятору необходимо еще раз выбрать специализациюfoo_impl
среди двух доступных; - В этом вопросе в условиях включения второй специализации
foo_impl
компилятор снова встречает условиеhas_foo<unsigned>::value
. - GOTO 3.
Однако, похоже, что код с радостью согласился и на GCC 5.4 и Clang 3.8. См. Здесь: http://ideone.com/XClvYT
Я хотел бы понять, что здесь происходит. Я что-то не понимаю и рекурсия блокируется каким-то другим эффектом? Или, может быть, я запускаю какое-то неопределенное/определенное поведение?
Попробуйте изменить беззнаковое в вашем примере, чтобы плавать и посмотреть, что произойдет. После этого происходит смена замены. unsigned будет оцениваться по is_integral, чтобы быть правдой. Итак, я думаю, что он сначала проверяет второй foo_impl, тогда первый из них важен. – rtbaldwin
[Бесконечная рекурсия в создании шаблона - неопределенное поведение.] (Http://eel.is/c++draft/temp.inst#14) –