Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to have mutually referential C++ templates?

The Problem

I have the following two struct declarations:

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

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

and I'd like to find X and Y such that I get something like this after substitution:

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

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

But I'd like to do so without hard-coding Yin and Yang into one another's definitions, so X would be something like Yin<Yang<Yin<Yang<...>>>>.

I can do this without template arguments like:

struct Yin;
struct Yang;

struct Yin {
  Yang *m_ptr;
};

struct Yang {
  Yin *m_ptr;
};

But my real use-case is considerably more complex and I'd really like to make it generic. Does anyone know of a way to accomplish this? Or maybe see something obvious that I'm missing?

I've tagged this question as c++14 because I'm compiling the relevant code with clang with -std=c++1y and I am happy to use any c++11/c++14 features to make this work.

A would-be solution that doesn't compile.

Here is a solution that looks like it should work, but doesn't compile (and gives me useless error messages):

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;
}
like image 952
scpayson Avatar asked Aug 27 '14 23:08

scpayson


1 Answers

YES

Specialize Yin and Yang for when T is a template type and void is the template parameter, which causes Yin<Yang<void>> to point at a Yang<Yin<void>> and vice versa, but without any explicit reference to the other, so you can have as many of these types as you want. with only one specialization.

//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;
};

However, these specializations kick in for any template<void> type, so we need to apply SFINAE with a type trait:

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 {} 

Then comes this horrible part, which is absurdly complicated and ugly, and requires a pointless extra template parameter on the Yin/Yang types:

//here's 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;
};

Now Yin and Yang reference only themselves, and adding new recursive pointer types is trivial. Proof of compilation here: http://coliru.stacked-crooked.com/a/47ecd31e7d48f617

"But wait!" You exclaim, then I have to duplicate all my members! Not quite simply split Yin into a class with the members that are shared, and have it inherit from Yin_specialmembers<T>, which contains the members that need specialization. Easy.

like image 77
Mooing Duck Avatar answered Oct 19 '22 07:10

Mooing Duck