C ++: рекурсивная зависимость в псевдониме типа - PullRequest
0 голосов
/ 17 января 2019

Мне интересно, правильный ли следующий код. Он компилируется и запускается на моем компьютере, но я чувствую, что существует рекурсивная зависимость псевдонима типа ShowValueClass, определенного в ValueClass. Не могли бы вы объяснить, как компилятор смог решить эту проблему?

#include <iostream>

namespace tmp {
template <typename T>
struct ShowValueClass {
  void ShowValue() { std::cout << T::value << std::endl; }
};
} // namespace tmp

struct ValueClass {
  using ShowValueClass = tmp::ShowValueClass<ValueClass>;
  static constexpr int value = 42;
};

int main() {
  ValueClass::ShowValueClass s;
  s.ShowValue();
  return 0;
}

1 Ответ

0 голосов
/ 18 января 2019

Здесь нет рекурсии. Ваш класс tmp::ShowValueClass<T> работает только для любого типа T, имеющего член value, который может быть напечатан на cout (имеет право ostream& operator<<(ostream&, const T&) определено).

Тот факт, что вы добавили псевдоним типа ShowValueClass внутри ValueClass со ссылкой на tmp::ShowValueClass<ValueClass>, ничего не изменило. В этом контексте класс действует как пространство имен.

Подумайте, что случится, если вы извлечете ShowValueClass из ValueClass:

struct ValueClass {
  static constexpr int value = 42;
};

using ShowValueClass = tmp::ShowValueClass<ValueClass>;

В этом случае вы бы получили доступ к ShowValueClass непосредственно в main() вместо префикса с ValueClass::, как в вашем случае. Факты, которые tmp::ShowValueClass использует T::value и что псевдоним типа ShowValueClass находится в ValueClass, не имеют значения (в них нет ничего особенного).

Вся идея использования самого класса в качестве параметра шаблона класса шаблона широко распространена в C ++. На самом деле, существует шаблон с именем CRTP (Curily Recurring Template Pattern), в котором класс наследуется от класса шаблона с использованием в качестве параметра самого класса (из Википедии):


// The Curiously Recurring Template Pattern (CRTP)
template<class T>
class Base
{
    // methods within Base can use template to access members of Derived
};
class Derived : public Base<Derived>
{
    // ...
};

Вы можете проверить всю статью здесь: https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

Пример истинной рекурсивной зависимости

Теперь, чтобы завершить картину, я могу показать вам код, который не скомпилирует из-за рекурсивного определения:

template <typename T>
struct A {
   static constexpr int val = T::val;
};

struct B {
   static constexpr int val = A<B>::val;
};

int main() { }

Здесь A<T>::val зависит от T::val, и это нормально, но B::val зависит от A<B>::val, который расширяется до B::val. Другими словами, B::val зависит от B::val, что явно не может быть разрешено . Это как сказать, что:

x := y(x)

с y(x):

y(x) := x. 

Следовательно, x := x. Ясно, что нет способа определить значение для x.

Рабочий пример использования рекурсии шаблона

Теперь, если рекурсия выполнена правильно и определен базовый случай , ясно, что его можно использовать даже для вычисления довольно сложных вещей. Вот тривиальный пример:

#include <iostream>
using namespace std;

template <int N>
struct A {
   static constexpr int val = N + A<N - 1>::val;
};

template <>
struct A<0> {
   static constexpr int val = 0;
};

int main() {
   cout << A<10>::val << endl;
}

A<N>::val определяется рекурсивно как:

N + A<N-1>::val if N != 0
0               if N == 0

И, как результат, это сумма всех чисел от 0 до N (включительно).

...