Я пытаюсь понять, почему кусок метапрограммирования шаблона не, создающий бесконечную рекурсию. Я попытался как можно больше сократить тестовый сценарий, но все еще есть немного настройки, поэтому несите меня:)
Настройка следующая. У меня есть общая функция 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
будет истинным, если для T
существует действительная специализация foo_impl
, иначе 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 и clang both) принимают следующий код:
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
Я хотел бы понять, что здесь происходит. Я что-то не понимаю и рекурсия блокируется каким-то другим эффектом? Или, может быть, я запускаю какое-то своеобразное поведение undefined/реализация?