Возможно ли иметь взаимно референтные шаблоны С++?
Проблема
У меня есть следующие две объявления структуры:
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
- я использовал их имена, но не нуждался.