Возможно ли иметь взаимно референтные шаблоны С++?

Проблема

У меня есть следующие две объявления структуры:

template <typename T>
struct Yin {
  T *m_ptr;
};

template <typename T>
struct Yang {
  T *m_ptr;
};

и я бы хотел найти X и Y таким образом, что после замены получим что-то вроде этого:

// Not real C++ syntax
struct Yin<X> {
  Yang<Y> *m_ptr;
}

struct Yang<Y> {
  Yin<X> *m_ptr;
};

Но я бы хотел сделать это без жесткого кодирования Yin и Yang в одном другом определении, поэтому X будет чем-то вроде Yin<Yang<Yin<Yang<...>>>>.

Я могу сделать это без аргументов шаблона, например:

struct Yin;
struct Yang;

struct Yin {
  Yang *m_ptr;
};

struct Yang {
  Yin *m_ptr;
};

Но мой реальный случай использования значительно сложнее, и я бы очень хотел сделать его общим. Кто-нибудь знает, как это сделать? Или, может быть, увидеть что-то очевидное, что мне не хватает?

Я отметил этот вопрос как c++14, потому что я компилирую соответствующий код с clang с помощью -std=c++1y, и я счастлив использовать любые возможности С++ 11/С++ 14, чтобы сделать эту работу.

Будущее решение, которое не компилируется.

Вот решение, похожее на то, что оно должно работать, но не компилирует (и дает мне бесполезные сообщения об ошибках):

template <typename T>
struct Yin {
  T *m_ptr;
};

template <typename T>
struct Yang {
  T *m_ptr;
};

template <template <class> class A, template <class> class B>
struct knot {
  using type = A<typename knot<B, A>::type>;
};

template <template <class> class A, template <class> class B>
using Tie = typename knot<A, B>::type;

int main() {
  // foo.cc:13:39: error: no type named 'type' in 'knot<Yin, Yang>'
  //  using type = A<typename knot<B, A>::type>;
  //                 ~~~~~~~~~~~~~~~~~~~~~^~~~
  Tie<Yin, Yang> x;
}

Ответ 1

ДА

Специализируйте Инь и Ян, когда T является типом шаблона, а void является параметром шаблона, который заставляет Yin<Yang<void>> указывать на a Yang<Yin<void>> и наоборот, но без какой-либо конкретной ссылки на другую, поэтому вы можете иметь столько из этих типов, сколько хотите. с одной специализацией.

//special recursive case
template <template<class> class other>
struct Yin<other<void>> 
{
    other<Yin<void>> *m_ptr;
};

template <template<class> class other>
struct Yang<other<void>> 
{
    other<Yang<void>> *m_ptr;
};

Однако эти специализации применяются для любого типа template<void>, поэтому нам нужно применить SFINAE с типом типа:

template<template<class> class T> struct is_yinyang : public std::false_type {};
template<> struct is_yinyang<Yin> : public std::true_type {}; 
template<> struct is_yinyang<Yang> : public std::true_type {} 

Затем возникает эта ужасная часть, которая абсурдно сложна и уродлива и требует бессмысленного дополнительного параметра шаблона для типов Yin/Yang:

//here Yin + Specialization
template <typename T, class allowed=void>
struct Yin {
    T *m_ptr;
};
template<> struct is_yinyang<Yin> : public std::true_type {};

template <template<class,class> class other>
struct Yin<other<void,void>,typename std::enable_if<is_yinyang<other>::value>::type>
{
    other<Yin<void,void>,void> *m_ptr;
};

Теперь Инь и Ян ссылаются только на себя, и добавление новых рекурсивных типов указателей тривиально. Подтверждение компиляции здесь: http://coliru.stacked-crooked.com/a/47ecd31e7d48f617

"Но подождите!" Вы восклицаете, тогда я должен дублировать всех своих членов! Не просто разделите Yin на класс с членами, которые являются общими, и наследуют его от Yin_specialmembers<T>, который содержит членов, нуждающихся в специализации. Легко.

Ответ 2

Да. Во-первых, научите Yin и Yang, как принять произвольное отображение для типа, который они хранят:

template<class T>struct identity{using type=T;};

template <typename T, template<class>class Z=identity>
struct Yin {
  template<class U>using Z_t=typename Z<U>::type;
  Z_t<T> *m_ptr;
};

template <typename T, template<class>class Z=identity>
struct Yang {
  template<class U>using Z_t=typename Z<U>::type;
  Z_t<T> *m_ptr;
};

со значением по умолчанию "просто возьмите T".

Затем напишите функцию типа мета-шаблона, которую мы можем передать в Yin и Yang:

template<
  template<class, template<class>class>class Yin,
  template<class, template<class>class>class Yang
>
struct flopper {
  template<class T> struct flip {
    using type = Yang<T, flopper<Yang, Yin>::template flip>;
  };
};

который принимает две вещи, такие как Yin и Yang, и переключает, какой из них он применяет каждый раз для своего второго аргумента.

И проверить это:

using yin = Yin< void, flopper<Yin, Yang>::template flip >;
using yang = Yang< void, flopper<Yang, Yin>::template flip >;
yin a = {nullptr};
yang b = {&a};

мы создаем типы, а компилируется.

Попытки удалить некоторые из синтаксических ошибок, по крайней мере, в этой версии gcc.

Обратите внимание, что тип на бесконечности - void выше - может быть изменен, и возникают различные типы.

В этом случае существует нет соединение между Yin и Yang, и помимо этого сопоставления ничего не нужно делать для двух типов, о которых идет речь. Единственное соединение находится в функции типа метатега flopper. Функция типа отображения даже относительно агностична для Yin и Yang - я использовал их имена, но не нуждался.