С++ 11: перегрузка не позволяет разрешить рекурсивный decltype

В следующем фрагменте кода я пытаюсь построить решетку типов. Например, между float и int, продвиньте результат до float:

float join(float f, int)   { return f; }
float join(float f, float) { return f; }

Затем введем тип wrapper:

template <typename Inner>
struct wrapper
{
  using inner_t = Inner;
  inner_t value;
};

поведение которого с операцией join вполне естественно:

template <typename Inner1, typename Inner2>
auto
join(const wrapper<Inner1>& w1, const wrapper<Inner2>& w2)
  -> wrapper<decltype(join(w1.value, w2.value))>
{
  return {join(w1.value, w2.value)};
}

Он также может быть join ed с "скалярным" типом:

template <typename Inner1, typename T2>
auto
join(const wrapper<Inner1>& w1, const T2& value2)
  -> wrapper<decltype(join(w1.value, value2))>
{
  return {join(w1.value, value2)};
}

Пока, так хорошо, это работает. Но тогда, поскольку в реальном случае у меня на самом деле есть еще много таких правил, я бы хотел избежать дублирования числа правил, чтобы выразить коммутативность операции join, и поэтому я выражаю, что join(scalar, wrapper) := join(wrapper, scalar) (на самом деле, Я бы предпочел что-то вроде join(v1, v2) := join(v2, v1), но начнем с чего-то более конкретного.):

template <typename T1, typename Inner2>
auto
join(const T1& value1, const wrapper<Inner2>& w2)
  -> decltype(join(w2, value1))
{
  return join(w2, value1);
}

это работает правильно для join(scalar, scalar), join(wrapper, scalar) и join(scalar, wrapper). Но тогда join(wrapper, wrapper) приводит к бесконечным разложениям шаблонных функций как с g++ 4.9, так и с Clang++ 3.5, которые я не понимаю.

int main()
{
  int i;
  float f;
  wrapper<float> fr;
  join(f, i);
  join(fr, i);
  join(i, fr);
  join(fr, fr); // Loops.
}

Clang:

clang++-mp-3.5 -std=c++11 bar.cc
bar.cc:21:5: fatal error: recursive template instantiation exceeded maximum depth of
      256
    join(const wrapper<Inner1>& w1, const T2& value2)
    ^
bar.cc:29:5: note: while substituting deduced template arguments into function
      template 'join' [with T1 = wrapper<float>, Inner2 = float]
    join(const T1& value1, const wrapper<Inner2>& w2)
    ^

GCC:

g++-mp-4.9 -std=c++11 bar.cc
bar.cc:30:34: error: template instantiation depth exceeds maximum of 900 (use -ftemplate-depth= to increase the maximum) substituting 'template<class T1, class Inner2> decltype (join(w2, value1)) join(const T1&, const wrapper<Inner2>&) [with T1 = <missing>; Inner2 = <missing>]'
       -> decltype(join(w2, value1))
                                  ^
bar.cc:30:34:   recursively required by substitution of 'template<class T1, class Inner2> decltype (join(w2, value1)) join(const T1&, const wrapper<Inner2>&) [with T1 = wrapper<float>; Inner2 = float]'
bar.cc:30:34:   required by substitution of 'template<class T1, class Inner2> decltype (join(w2, value1)) join(const T1&, const wrapper<Inner2>&) [with T1 = wrapper<float>; Inner2 = float]'
bar.cc:43:18:   required from here

Я не понимаю, почему перегрузка не режет рекурсию. Что происходит? Вероятно, есть возможная альтернативная реализация с помощью специализированной (класс), но я не ищу альтернативных реализаций: я хотел бы понять, что с этим не так. Спасибо заранее.

Ответ 1

С этим связано несколько проблем, и один из них приводит к ошибке.

template <typename Inner1, typename T2>
auto
join(const wrapper<Inner1>& w1, const T2& value2)  // (A)
  -> wrapper<decltype(join(w1.value, value2))>;

Поиск имени join здесь не найдет тот же самый шаблон функции через неквалифицированный поиск, поскольку тип возвращаемого типа является частью объявления, а имена могут быть найдены только после того, как они имеют. Но синтаксис позволяет ADL находить один и тот же шаблон функции. ADL для зависимых имен выполняется позже (с момента создания).

Насколько я понял эту проблему, проблема возникает из-за разрешения перегрузки: Прежде чем decltype(join(w1.value, value2)) пытается разрешить перегрузку, все шаблоны функций с этим именем должны быть созданы. Для каждого шаблона функции добавляется один экземпляр в набор перегрузки (если создание экземпляра завершается успешно).

Следовательно, все join должны быть созданы. Мгновение включает определение типа возврата. Для каждого экземпляра этого конкретного шаблона функции join (A) тот же шаблон функции (A) с теми же аргументами шаблона является кандидатом на набор разрешений перегрузки. То есть, чтобы определить, какой тип возврата (A) имеет, должно быть разрешение перегрузки, что требует определения типа возврата (A) и т.д.

Дедукция и подстановка никогда не прерываются ни на одном шаге рекурсии, единственная причина не выбирать эту перегрузку - это частичный порядок между различными шаблонами функций, называемыми join. Частичное упорядочение проверяется только как часть процесса разрешения перегрузки - это слишком поздно, чтобы предотвратить дальнейшие экземпляры.

Эта ошибка, как указано в сообщении об ошибке, встречается как предел реализации. Поэтому он не попадает в категорию SFINAE, см. Решение проблемы SFINAE для выражений. Поэтому, даже если эта перегрузка не выбрана, ее простое существование делает программу плохо сформированной, как и

struct tag_for_ADL {};

template<class T>
auto foo(T p) -> decltype(foo(p));

foo(tag_for_ADL{}); // ill-formed, leads to infinite recursive instantiation

Ответ 2

Определите шаблон join_traits. Специализируйтесь на скалярах и на обертках. Используйте

join_traits<T1>::type
join_traits<T2>::type

и/или

join_traits<T1>::get_value(v1)
join_traits<T2>::get_value(v2)

в шаблоне одной функции

template<class T1, class T2>
auto join (T1 v1, T2 v2) -> ...