The following is a type system of layers (A, B, C, Null) and nodes for a library.

A version that prints colored text for debugging is on github.

Thanks in advance for reviewing this.


The user can create a stack of layers:

  • The type of a stack can be C<B<A<>>>, B<C<A<>>>, B<A> or C<A>
  • The lowest layer of a stack must be a memory layer (e.g. A in code).

Conversions between nodes produced by any layers of a stack is possible.

  • For examaple, consider a stack C<B<A<>>>:

    • The 3 layers of the stack would create 3 kinds of nodes, called Na, Nb, Nc.
    • Na, Nb, Nc can convert to each other.
    • The conversion should happen stepwise. For example, conversionNa->Nc involves conversions Na->Nb->Nc
    • Na, Nb, Nc are respectively instantiation of class templates Node_a, Node_b, Node_c
    • layer_type::node_type are Na, Nb, Nc for the 3 layers.

Can write functions that only accept instantiation of a node template. This is enabled by std::enable_if_t and traits in the library.

  • is_node<T> checks if type T is a node.
  • converted_node_is_instantiation_of<T, Node_template> checks if some conversion of T is an instantiation of the Node_template.
  • can_instantiate_to<template, T> checks if T is an instantiaion of the template.

Use case

  • Library of stackable layers with proxies for manipulating the layers.


  • The is_node trait is not quite extensible.

  • Constraining multiple arguments of a function to accept only instantiation of some node_templates is tedious. Example for constraining just one argument:

template<typename T,
                node_is_instantiation_of<T, Node_c>()> >
void foo(const T& c) {
    // convert c to an actual instantiation of Node_c ...
  • A possible alternative is a helper that checks with static_assert. This helper would also handle type conversion of nodes. But that has other problems as well:
// Possible api
void foo(const T& c) {
    auto c2 = convert_node<Node_c>(c);

// void foo(const T& b) {  // can't overload this way.
//     auto b2 = convert_node<Node_b>(b);
// }
  • The compilation error message for conversions between node types is not very clear. Whether a conversion between nodes is possible actually depends on the stack of layers. But the stack of layers is not shown in the error message.

  • Colored error message in run time won't help if the program doesn't compile.

  • No specification for node, const_node, view_of_node, and view_of_const_node.


#include <iostream>
#include <type_traits>

// -- [[ Forward declarations ]] -- //
template<class> struct Node_a;
template<class> struct Node_b;
template<class> struct Node_c;
struct Node_null;

template<class, class, int> struct B;
template<class, class> struct C;
struct Null;

// -- [[ Node converters ]] -- //
template<class Self, class Next>
struct Chained_converter {
    template<class Destination,
            class = std::enable_if_t<
                    std::is_constructible_v<Destination, Next>
                    && !std::is_same<Destination, Null>::value
    operator Destination() {
        return static_cast<Self*>(this)->operator Next();

template<class L>
struct Node_converter
        : Chained_converter<class L::node_type,
                class L::prev_type::node_type>,
          Chained_converter<class L::node_type,
                  class L::next_type::node_type> {

template<typename Node> using Promote_node = typename

template<typename Node> using Demote_node = typename

// -- [[ Node ]] -- //
struct Node_null {
    using layer_type=Null;

template <class Self, class L>
class Node_base : public Node_converter<L> {
    using next_type = typename L::next_type::node_type;
    using prev_type = typename L::prev_type::node_type;
    operator prev_type() { return prev_type(); }
    operator next_type() { return next_type(); }

template<class L>
struct Node_a : public Node_base<Node_a<L>, L> {
    using layer_type = L;
    using Node_base<Node_a<L>, L>::Node_base;

template<class L>
struct Node_b : public Node_base<Node_b<L>, L> {
    using layer_type = L;
    using Node_base<Node_b<L>, L>::Node_base;

template<class L>
struct Node_c : public Node_base<Node_c<L>, L> {
    using layer_type = L;
    using Node_base<Node_c<L>, L>::Node_base;

// -- [[ Layers ]] --
struct Null {
    using node_type = Node_null;

template<class Raw_prev=Null, class Next=Null>
struct A {
    using self_type = A<Raw_prev, Next>;
    using node_type = Node_a<self_type>;
    using prev_type = Null;
    using next_type = Next;
    template<class New_next>
    using rebase = A<Raw_prev, New_next>;

template<class Raw_prev, class Next=Null, int Parm = 1>
struct B {
    using self_type = B<Raw_prev, Next, Parm>;
    using node_type = Node_b<self_type>;
    using prev_type = typename Raw_prev::template rebase<self_type>;
    using next_type = Next;
    template<class New_next>
    using rebase = B<Raw_prev, New_next, Parm>;

template<class Raw_prev, class Next=Null>
struct C {
    using self_type = C<Raw_prev, Next>;
    using node_type = Node_c<self_type>;
    using prev_type = typename Raw_prev::template rebase<self_type>;
    using next_type = Next;
    template<class New_next> using rebase = C<Raw_prev, New_next>;

// -- [[ Node Traits ]] -- //
template<template<typename...> class, typename...>
struct can_instantiate_to : public std::false_type {

template<template<typename...> class U, typename... T>
struct can_instantiate_to<U, U<T...>> : public std::true_type {

template<typename L>
struct is_node
        : std::integral_constant<bool,
                can_instantiate_to<Node_a, L>::value
                || can_instantiate_to<Node_b, L>::value
                || can_instantiate_to<Node_c, L>::value
        > {};

template<typename Node, template<typename...> class Node_template>
constexpr bool is_up_instantiation_of() {
    if constexpr (std::is_same<Node, Node_null>::value) {
        return false;
    } else {
        if constexpr (can_instantiate_to<Node_template, Node>::value) {
            return true;
        } else {
            return is_up_instantiation_of<
                    Promote_node<Node>, Node_template>();

template<typename Node, template<typename...> class Node_template>
constexpr bool is_down_instantiation_of() {
    if constexpr (std::is_same<Node, Node_null>::value) {
        return false;
    } else {
        if constexpr (can_instantiate_to<Node_template, Node>::value) {
            return true;
        } else {
            return is_down_instantiation_of<
                    Demote_node<Node>, Node_template>();

template<typename T, template<typename...> class Node_template>
constexpr bool converted_node_is_instantiation_of() {
    return is_node<T>::value
           && (is_up_instantiation_of<T, Node_template>()
           || is_down_instantiation_of<T, Node_template>());

// -- [[ Test ]] -- //

template<typename T,
                converted_node_is_instantiation_of<T, Node_c>()> >
void foo(const T& c) {}

int main() {
    // Create a types for a stack of layers.
    using Stack_c = C<B<A<>>>;
    using Partial_stack_b = typename Stack_c::prev_type;
    using Partial_stack_a = typename Partial_stack_b::prev_type;

    typename Stack_c::node_type c;
    typename Partial_stack_a::node_type a;

    // Test chained type conversions
    c = a;  // Node_c->b->a
    a = c;  // Node_a->b->c

    // Test constraining parameter of a function

    // Create types for 2nd stack of layers.
    using Stack2_c = C<A<>>;
    using Partial_stack2_a = typename Stack2_c::prev_type;

    // Should be false because Stack2_c::node_type is a node
    // but it is not an instantiation of the Node_b template.
    std::cout << converted_node_is_instantiation_of<
            Stack2_c::node_type, Node_b>();
  • \$\begingroup\$ How much can concepts simplify this? I haven't looked up how to use concepts yet. \$\endgroup\$ – R zu May 14 '18 at 0:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.