The C++ Template Interface Pattern (TIP)

The C++ Template Interface Pattern (TIP) is the application of the Curiously Recurring Template Pattern (CRTP) to a set of one or more public “interface” classes nested inside a public “set of interfaces” class where the “set of interfaces” class uses template template parameters to pass in “implementation” classes.

The advantages of using the C++ Template Interface Pattern (TIP) which leverages compile-time indirection over the traditional C++ interface pattern that uses run-time indirection (i.e. inheriting from classes that only contain pure virtual functions and indirecting through a pointer to a table of virtual function pointers at run-time to perform member function calls) is that:

  1. For optimized builds (i.e. “Release” builds), programs using the C++ Template Interface Pattern (TIP) are faster than programs using the traditional C++ interface pattern.  Code using the TIP is optimized down to fewer assembly language instructions than code using the traditional interface pattern due to the relative ease of optimizing the compile-time indirection used by the TIP compared to the run-time indirection used in the traditional case.
  2. Classes using the C++ Template Interface Pattern (TIP) are smaller than their traditional C++ interface pattern equivalents.  TIP classes do not contain a pointer to a table of virtual function pointers; therefore, TIP classes are consistently eight bytes smaller for x64 builds and four bytes smaller for x86 builds.
  3. If Run-Time Polymorphism is needed, it is trivial to add the traditional C++ interface pattern on top of the C++ Template Interface Pattern (TIP); however, the size advantage from the previous point will be lost.  In other words, TIP and traditional interfaces are compatible.

Here’s a quick example of the C++ Template Interface Pattern (TIP) to implement a trivial version of the Layers Architectural Pattern:

#include <iostream>
#include <memory>
 
#ifdef __FUNCSIG__
#define PRINT_FUNCTION std::wcout << __FUNCSIG__ << '\n'
#else
#define PRINT_FUNCTION std::wcout << __func__ << '\n'
#endif
 
template
<
    template <typenametypenametypenametypename Layer1Template,
    template <typenametypenametypenametypename Layer2Template,
    template <typenametypenametypenametypename Layer3Template
>
class Layers // A set of related interfaces
{
public:
    class ILayer1;
    class ILayer2;
    class ILayer3;
 
private:
    using ILayer1LowerLayer  = std::nullptr_t;
    using ILayer1HigherLayer = ILayer2;
 
    using ILayer2LowerLayer  = ILayer1;
    using ILayer2HigherLayer = ILayer3;
 
    using ILayer3LowerLayer  = ILayer2;
    using ILayer3HigherLayer = std::nullptr_t;
 
    using Layer1 = Layer1Template<ILayer1ILayer1LowerLayerILayer1HigherLayer>; // CRTP
    using Layer2 = Layer2Template<ILayer2ILayer2LowerLayerILayer2HigherLayer>; // CRTP
    using Layer3 = Layer3Template<ILayer3ILayer3LowerLayerILayer3HigherLayer>; // CRTP
 
public:
    class ILayer1 : public Layer1
    {
    private:
        using This        = Layer1;
        using LowerLayer  = ILayer1LowerLayer;
        using HigherLayer = ILayer1HigherLayer;
 
    public:
        constexpr void SetLowerLayer(LowerLayerlower_layer_ptrnoexcept
        {
            return This::SetLowerLayer(lower_layer_ptr);
        }
 
        constexpr void SetHigherLayer(HigherLayerhigher_layer_ptrnoexcept
        {
            return This::SetHigherLayer(higher_layer_ptr);
        }
 
        constexpr void Down() noexcept
        {
            return This::Down();
        }
 
        constexpr void TransitionFromDownToUp() noexcept
        {
            return This::TransitionFromDownToUp();
        }
 
        constexpr void Up() noexcept
        {
            return This::Up();
        }
    };
 
    class ILayer2 : public Layer2
    {
    private:
        using This        = Layer2;
        using LowerLayer  = ILayer2LowerLayer;
        using HigherLayer = ILayer2HigherLayer;
 
    public:
        constexpr void SetLowerLayer(LowerLayerlower_layer_ptrnoexcept
        {
            return This::SetLowerLayer(lower_layer_ptr);
        }
 
        constexpr void SetHigherLayer(HigherLayerhigher_layer_ptrnoexcept
        {
            return This::SetHigherLayer(higher_layer_ptr);
        }
 
        constexpr void Down() noexcept
        {
            return This::Down();
        }
 
        constexpr void Up() noexcept
        {
            return This::Up();
        }
 
        constexpr int Value() const noexcept
        {
            return This::Value();
        }
 
        constexpr void Value(int valuenoexcept
        {
            return This::Value(value);
        }
    };
 
    class ILayer3 : public Layer3
    {
    private:
        using This        = Layer3;
        using LowerLayer  = ILayer3LowerLayer;
        using HigherLayer = ILayer3HigherLayer;
 
    public:
        constexpr void SetLowerLayer(LowerLayerlower_layer_ptrnoexcept
        {
            return This::SetLowerLayer(lower_layer_ptr);
        }
 
        constexpr void SetHigherLayer(HigherLayerhigher_layer_ptrnoexcept
        {
            return This::SetHigherLayer(higher_layer_ptr);
        }
 
        constexpr void Down() noexcept
        {
            return This::Down();
        }
 
        constexpr void Up() noexcept
        {
            return This::Up();
        }
 
        constexpr int GetLayer2Value() const noexcept
        {
            return This::GetLayer2Value();
        }
 
        constexpr void SetLayer2Value(int valuenoexcept
        {
            return This::SetLayer2Value(value);
        }
    };
};
 
template <typename Thistypename LowerLayertypename HigherLayer>
class Layer1
{
private:
    LowerLayer*  m_lower_layer_ptr{ nullptr };
    HigherLayer* m_higher_layer_ptr{ nullptr };
 
public:
    Layer1()                          = default;
    Layer1(const Layer1&)             = delete;
    Layer1(Layer1&&)                  = delete;
    ~Layer1()                         = default;
    Layer1operator =(const Layer1&) = delete;
    Layer1operator =(Layer1&&)      = delete;
 
    constexpr void SetLowerLayer(LowerLayerlower_layer_ptrnoexcept
    {
        m_lower_layer_ptr = lower_layer_ptr;
    }
 
    constexpr void SetHigherLayer(HigherLayerhigher_layer_ptrnoexcept
    {
        m_higher_layer_ptr = higher_layer_ptr;
    }
 
    constexpr void Down() noexcept
    {
        PRINT_FUNCTION;
        TransitionFromDownToUp();
    }
 
    constexpr void TransitionFromDownToUp() noexcept
    {
        PRINT_FUNCTION;
        Up();
    }
 
    constexpr void Up() noexcept
    {
        PRINT_FUNCTION;
        m_higher_layer_ptr->Up();
    }
};
 
template <typename Thistypename LowerLayertypename HigherLayer>
class Layer2
{
private:
    LowerLayer*  m_lower_layer_ptr{ nullptr };
    HigherLayer* m_higher_layer_ptr{ nullptr };
    int          m_value{ 123 };
 
public:
    Layer2()                          = default;
    Layer2(const Layer2&)             = delete;
    Layer2(Layer2&&)                  = delete;
    ~Layer2()                         = default;
    Layer2operator =(const Layer2&) = delete;
    Layer2operator =(Layer2&&)      = delete;
 
    constexpr void SetLowerLayer(LowerLayerlower_layer_ptrnoexcept
    {
        m_lower_layer_ptr = lower_layer_ptr;
    }
 
    constexpr void SetHigherLayer(HigherLayerhigher_layer_ptrnoexcept
    {
        m_higher_layer_ptr = higher_layer_ptr;
    }
 
    constexpr void Down() noexcept
    {
        PRINT_FUNCTION;
        m_lower_layer_ptr->Down();
    }
 
    constexpr void Up() noexcept
    {
        PRINT_FUNCTION;
        m_higher_layer_ptr->Up();
    }
 
    constexpr int Value() const noexcept
    {
        //PRINT_FUNCTION;
        return m_value;
    }
 
    constexpr void Value(int valuenoexcept
    {
        //PRINT_FUNCTION;
        m_value = value;
    }
};
 
template <typename Thistypename LowerLayertypename HigherLayer>
class Layer3
{
private:
    LowerLayer*  m_lower_layer_ptr{ nullptr };
    HigherLayer* m_higher_layer_ptr{ nullptr };
 
public:
    Layer3()                          = default;
    Layer3(const Layer3&)             = delete;
    Layer3(Layer3&&)                  = delete;
    ~Layer3()                         = default;
    Layer3operator =(const Layer3&) = delete;
    Layer3operator =(Layer3&&)      = delete;
 
    constexpr void SetLowerLayer(LowerLayerlower_layer_ptrnoexcept
    {
        m_lower_layer_ptr = lower_layer_ptr;
    }
 
    constexpr void SetHigherLayer(HigherLayerhigher_layer_ptrnoexcept
    {
        m_higher_layer_ptr = higher_layer_ptr;
    }
 
    constexpr void Down() noexcept
    {
        PRINT_FUNCTION;
        m_lower_layer_ptr->Down();
    }
 
    constexpr void Up() noexcept
    {
        PRINT_FUNCTION;
    }
 
    constexpr int GetLayer2Value() const noexcept
    {
        //PRINT_FUNCTION;
        return m_lower_layer_ptr->Value();
    }
 
    constexpr void SetLayer2Value(int valuenoexcept
    {
        //PRINT_FUNCTION;
        return m_lower_layer_ptr->Value(value);
    }
};
 
int main()
{
    using ILayers = Layers<Layer1Layer2Layer3>;
 
    auto layer1_ptr{ std::make_unique<ILayers::ILayer1>() };
    auto layer2_ptr{ std::make_unique<ILayers::ILayer2>() };
    auto layer3_ptr{ std::make_unique<ILayers::ILayer3>() };
 
    layer1_ptr->SetLowerLayer(nullptr);
    layer1_ptr->SetHigherLayer(layer2_ptr.get());
 
    layer2_ptr->SetLowerLayer(layer1_ptr.get());
    layer2_ptr->SetHigherLayer(layer3_ptr.get());
 
    layer3_ptr->SetLowerLayer(layer2_ptr.get());
    layer3_ptr->SetHigherLayer(nullptr);
 
    layer3_ptr->Down();
 
    std::wcout << layer3_ptr->GetLayer2Value() << '\n';
 
    int value{ 0 };
    std::wcin >> value;
 
    layer3_ptr->SetLayer2Value(value);
 
    std::wcout << layer3_ptr->GetLayer2Value() << '\n';
}

When the code above is compiled with g++ and run, it produces the following output (assuming an input of “456”):

Down
Down
Down
TransitionFromDownToUp
Up
Up
Up
123
456
456

Here’s that example of the C++ Template Interface Pattern (TIP) again combined with the traditional C++ interface pattern to produced a section of code using Run-Time Polymorphism:

#include <iostream>
#include <memory>
#include <vector>
 
#ifdef __FUNCSIG__
#define PRINT_FUNCTION std::wcout << __FUNCSIG__ << '\n'
#else
#define PRINT_FUNCTION std::wcout << __func__ << '\n'
#endif
 
template
<
    template <typenametypenametypenametypename Layer1Template,
    template <typenametypenametypenametypename Layer2Template,
    template <typenametypenametypenametypename Layer3Template
>
class Layers // A set of related interfaces
{
public:
    class ILayer1;
    class ILayer2;
    class ILayer3;
 
private:
    using ILayer1LowerLayer  = std::nullptr_t;
    using ILayer1HigherLayer = ILayer2;
 
    using ILayer2LowerLayer  = ILayer1;
    using ILayer2HigherLayer = ILayer3;
 
    using ILayer3LowerLayer  = ILayer2;
    using ILayer3HigherLayer = std::nullptr_t;
 
    using Layer1 = Layer1Template<ILayer1ILayer1LowerLayerILayer1HigherLayer>; // CRTP
    using Layer2 = Layer2Template<ILayer2ILayer2LowerLayerILayer2HigherLayer>; // CRTP
    using Layer3 = Layer3Template<ILayer3ILayer3LowerLayerILayer3HigherLayer>; // CRTP
 
public:
    class ILayer1 : public Layer1
    {
    private:
        using This        = Layer1;
        using LowerLayer  = ILayer1LowerLayer;
        using HigherLayer = ILayer1HigherLayer;
 
    public:
        constexpr void SetLowerLayer(LowerLayerlower_layer_ptrnoexcept
        {
            return This::SetLowerLayer(lower_layer_ptr);
        }
 
        constexpr void SetHigherLayer(HigherLayerhigher_layer_ptrnoexcept
        {
            return This::SetHigherLayer(higher_layer_ptr);
        }
 
        constexpr void Down() noexcept
        {
            return This::Down();
        }
 
        constexpr void TransitionFromDownToUp() noexcept
        {
            return This::TransitionFromDownToUp();
        }
 
        constexpr void Up() noexcept
        {
            return This::Up();
        }
    };
 
    class ILayer2 : public Layer2
    {
    private:
        using This        = Layer2;
        using LowerLayer  = ILayer2LowerLayer;
        using HigherLayer = ILayer2HigherLayer;
 
    public:
        constexpr void SetLowerLayer(LowerLayerlower_layer_ptrnoexcept
        {
            return This::SetLowerLayer(lower_layer_ptr);
        }
 
        constexpr void SetHigherLayer(HigherLayerhigher_layer_ptrnoexcept
        {
            return This::SetHigherLayer(higher_layer_ptr);
        }
 
        constexpr void Down() noexcept
        {
            return This::Down();
        }
 
        constexpr void Up() noexcept
        {
            return This::Up();
        }
 
        constexpr int Value() const noexcept
        {
            return This::Value();
        }
 
        constexpr void Value(int valuenoexcept
        {
            return This::Value(value);
        }
    };
 
    class ILayer3 : public Layer3
    {
    private:
        using This        = Layer3;
        using LowerLayer  = ILayer3LowerLayer;
        using HigherLayer = ILayer3HigherLayer;
 
    public:
        constexpr void SetLowerLayer(LowerLayerlower_layer_ptrnoexcept
        {
            return This::SetLowerLayer(lower_layer_ptr);
        }
 
        constexpr void SetHigherLayer(HigherLayerhigher_layer_ptrnoexcept
        {
            return This::SetHigherLayer(higher_layer_ptr);
        }
 
        constexpr void Down() noexcept
        {
            return This::Down();
        }
 
        constexpr void Up() noexcept
        {
            return This::Up();
        }
 
        constexpr int GetLayer2Value() const noexcept
        {
            return This::GetLayer2Value();
        }
 
        constexpr void SetLayer2Value(int valuenoexcept
        {
            return This::SetLayer2Value(value);
        }
    };
};
 
struct IForRuntimePolymorphism
{
    virtual ~IForRuntimePolymorphism() = default;
    virtual void DoSomething() noexcept = 0;
};
 
template <typename Thistypename LowerLayertypename HigherLayer>
class Layer1 : public IForRuntimePolymorphism
{
private:
    LowerLayer*  m_lower_layer_ptr{ nullptr };
    HigherLayer* m_higher_layer_ptr{ nullptr };
 
public:
    Layer1()                          = default;
    Layer1(const Layer1&)             = delete;
    Layer1(Layer1&&)                  = delete;
    ~Layer1()                         = default;
    Layer1operator =(const Layer1&) = delete;
    Layer1operator =(Layer1&&)      = delete;
 
    constexpr void SetLowerLayer(LowerLayerlower_layer_ptrnoexcept
    {
        m_lower_layer_ptr = lower_layer_ptr;
    }
 
    constexpr void SetHigherLayer(HigherLayerhigher_layer_ptrnoexcept
    {
        m_higher_layer_ptr = higher_layer_ptr;
    }
 
    constexpr void Down() noexcept
    {
        PRINT_FUNCTION;
        TransitionFromDownToUp();
    }
 
    constexpr void TransitionFromDownToUp() noexcept
    {
        PRINT_FUNCTION;
        Up();
    }
 
    constexpr void Up() noexcept
    {
        PRINT_FUNCTION;
        m_higher_layer_ptr->Up();
    }
 
    virtual void DoSomething() noexcept override
    {
        PRINT_FUNCTION;
    }
};
 
template <typename Thistypename LowerLayertypename HigherLayer>
class Layer2 : public IForRuntimePolymorphism
{
private:
    LowerLayer*  m_lower_layer_ptr{ nullptr };
    HigherLayer* m_higher_layer_ptr{ nullptr };
    int          m_value{ 123 };
 
public:
    Layer2()                          = default;
    Layer2(const Layer2&)             = delete;
    Layer2(Layer2&&)                  = delete;
    ~Layer2()                         = default;
    Layer2operator =(const Layer2&) = delete;
    Layer2operator =(Layer2&&)      = delete;
 
    constexpr void SetLowerLayer(LowerLayerlower_layer_ptrnoexcept
    {
        m_lower_layer_ptr = lower_layer_ptr;
    }
 
    constexpr void SetHigherLayer(HigherLayerhigher_layer_ptrnoexcept
    {
        m_higher_layer_ptr = higher_layer_ptr;
    }
 
    constexpr void Down() noexcept
    {
        PRINT_FUNCTION;
        m_lower_layer_ptr->Down();
    }
 
    constexpr void Up() noexcept
    {
        PRINT_FUNCTION;
        m_higher_layer_ptr->Up();
    }
 
    constexpr int Value() const noexcept
    {
        //PRINT_FUNCTION;
        return m_value;
    }
 
    constexpr void Value(int valuenoexcept
    {
        //PRINT_FUNCTION;
        m_value = value;
    }
 
    virtual void DoSomething() noexcept override
    {
        PRINT_FUNCTION;
    }
};
 
template <typename Thistypename LowerLayertypename HigherLayer>
class Layer3 : public IForRuntimePolymorphism
{
private:
    LowerLayer*  m_lower_layer_ptr{ nullptr };
    HigherLayer* m_higher_layer_ptr{ nullptr };
 
public:
    Layer3()                          = default;
    Layer3(const Layer3&)             = delete;
    Layer3(Layer3&&)                  = delete;
    ~Layer3()                         = default;
    Layer3operator =(const Layer3&) = delete;
    Layer3operator =(Layer3&&)      = delete;
 
    constexpr void SetLowerLayer(LowerLayerlower_layer_ptrnoexcept
    {
        m_lower_layer_ptr = lower_layer_ptr;
    }
 
    constexpr void SetHigherLayer(HigherLayerhigher_layer_ptrnoexcept
    {
        m_higher_layer_ptr = higher_layer_ptr;
    }
 
    constexpr void Down() noexcept
    {
        PRINT_FUNCTION;
        m_lower_layer_ptr->Down();
    }
 
    constexpr void Up() noexcept
    {
        PRINT_FUNCTION;
    }
 
    constexpr int GetLayer2Value() const noexcept
    {
        //PRINT_FUNCTION;
        return m_lower_layer_ptr->Value();
    }
 
    constexpr void SetLayer2Value(int valuenoexcept
    {
        //PRINT_FUNCTION;
        return m_lower_layer_ptr->Value(value);
    }
 
    virtual void DoSomething() noexcept override
    {
        PRINT_FUNCTION;
    }
};
 
int main()
{
    using ILayers = Layers<Layer1Layer2Layer3>;
 
    auto layer1_ptr{ std::make_unique<ILayers::ILayer1>() };
    auto layer2_ptr{ std::make_unique<ILayers::ILayer2>() };
    auto layer3_ptr{ std::make_unique<ILayers::ILayer3>() };
 
    layer1_ptr->SetLowerLayer(nullptr);
    layer1_ptr->SetHigherLayer(layer2_ptr.get());
 
    layer2_ptr->SetLowerLayer(layer1_ptr.get());
    layer2_ptr->SetHigherLayer(layer3_ptr.get());
 
    layer3_ptr->SetLowerLayer(layer2_ptr.get());
    layer3_ptr->SetHigherLayer(nullptr);
 
    layer3_ptr->Down();
 
    std::wcout << layer3_ptr->GetLayer2Value() << '\n';
 
    int value{ 0 };
    std::wcin >> value;
 
    layer3_ptr->SetLayer2Value(value);
 
    std::wcout << layer3_ptr->GetLayer2Value() << '\n';
 
    std::vector<std::unique_ptr<IForRuntimePolymorphism>> layer_ptrs{};
    layer_ptrs.emplace_back(std::move(layer1_ptr));
    layer_ptrs.emplace_back(std::move(layer2_ptr));
    layer_ptrs.emplace_back(std::move(layer3_ptr));
    for (auto&& layer_ptr : layer_ptrs)
    {
        layer_ptr->DoSomething();
    }
}

When the code above is compiled with g++ and run, it produces the following output (assuming an input of “456”):

Down
Down
Down
TransitionFromDownToUp
Up
Up
Up
123
456
456
DoSomething
DoSomething
DoSomething

Of course, if you’re able to leverage C++ 2017 (a.k.a. C++17) with its std::variant and std::visit, then it’s fairly easy to switch from Run-Time Polymorphism to Compile-Time Polymorphism:

#include <iostream>
#include <memory>
#include <vector>
#include <variant>
#include <type_traits>
 
#ifdef __FUNCSIG__
#define PRINT_FUNCTION std::wcout << __FUNCSIG__ << '\n'
#else
#define PRINT_FUNCTION std::wcout << __func__ << '\n'
#endif
 
template
<
    template <typenametypenametypenametypename Layer1Template,
    template <typenametypenametypenametypename Layer2Template,
    template <typenametypenametypenametypename Layer3Template
>
class Layers // A set of related interfaces
{
public:
    class ILayer1;
    class ILayer2;
    class ILayer3;
 
private:
    using ILayer1LowerLayer  = std::nullptr_t;
    using ILayer1HigherLayer = ILayer2;
 
    using ILayer2LowerLayer  = ILayer1;
    using ILayer2HigherLayer = ILayer3;
 
    using ILayer3LowerLayer  = ILayer2;
    using ILayer3HigherLayer = std::nullptr_t;
 
    using Layer1 = Layer1Template<ILayer1ILayer1LowerLayerILayer1HigherLayer>; // CRTP
    using Layer2 = Layer2Template<ILayer2ILayer2LowerLayerILayer2HigherLayer>; // CRTP
    using Layer3 = Layer3Template<ILayer3ILayer3LowerLayerILayer3HigherLayer>; // CRTP
 
public:
    class ILayer1 : public Layer1
    {
    private:
        using This        = Layer1;
        using LowerLayer  = ILayer1LowerLayer;
        using HigherLayer = ILayer1HigherLayer;
 
    public:
        constexpr void SetLowerLayer(LowerLayerlower_layer_ptrnoexcept
        {
            return This::SetLowerLayer(lower_layer_ptr);
        }
 
        constexpr void SetHigherLayer(HigherLayerhigher_layer_ptrnoexcept
        {
            return This::SetHigherLayer(higher_layer_ptr);
        }
 
        constexpr void Down() noexcept
        {
            return This::Down();
        }
 
        constexpr void TransitionFromDownToUp() noexcept
        {
            return This::TransitionFromDownToUp();
        }
 
        constexpr void Up() noexcept
        {
            return This::Up();
        }
 
        constexpr void DoSomething() noexcept
        {
            return This::DoSomething();
        }
    };
 
    class ILayer2 : public Layer2
    {
    private:
        using This        = Layer2;
        using LowerLayer  = ILayer2LowerLayer;
        using HigherLayer = ILayer2HigherLayer;
 
    public:
        constexpr void SetLowerLayer(LowerLayerlower_layer_ptrnoexcept
        {
            return This::SetLowerLayer(lower_layer_ptr);
        }
 
        constexpr void SetHigherLayer(HigherLayerhigher_layer_ptrnoexcept
        {
            return This::SetHigherLayer(higher_layer_ptr);
        }
 
        constexpr void Down() noexcept
        {
            return This::Down();
        }
 
        constexpr void Up() noexcept
        {
            return This::Up();
        }
 
        constexpr int Value() const noexcept
        {
            return This::Value();
        }
 
        constexpr void Value(int valuenoexcept
        {
            return This::Value(value);
        }
 
        constexpr void DoSomething() noexcept
        {
            return This::DoSomething();
        }
    };
 
    class ILayer3 : public Layer3
    {
    private:
        using This        = Layer3;
        using LowerLayer  = ILayer3LowerLayer;
        using HigherLayer = ILayer3HigherLayer;
 
    public:
        constexpr void SetLowerLayer(LowerLayerlower_layer_ptrnoexcept
        {
            return This::SetLowerLayer(lower_layer_ptr);
        }
 
        constexpr void SetHigherLayer(HigherLayerhigher_layer_ptrnoexcept
        {
            return This::SetHigherLayer(higher_layer_ptr);
        }
 
        constexpr void Down() noexcept
        {
            return This::Down();
        }
 
        constexpr void Up() noexcept
        {
            return This::Up();
        }
 
        constexpr int GetLayer2Value() const noexcept
        {
            return This::GetLayer2Value();
        }
 
        constexpr void SetLayer2Value(int valuenoexcept
        {
            return This::SetLayer2Value(value);
        }
 
        constexpr void DoSomething() noexcept
        {
            return This::DoSomething();
        }
    };
};
 
template <typename Thistypename LowerLayertypename HigherLayer>
class Layer1
{
private:
    LowerLayer*  m_lower_layer_ptr{ nullptr };
    HigherLayer* m_higher_layer_ptr{ nullptr };
 
public:
    Layer1()                          = default;
    Layer1(const Layer1&)             = delete;
    Layer1(Layer1&&)                  = delete;
    ~Layer1()                         = default;
    Layer1operator =(const Layer1&) = delete;
    Layer1operator =(Layer1&&)      = delete;
 
    constexpr void SetLowerLayer(LowerLayerlower_layer_ptrnoexcept
    {
        m_lower_layer_ptr = lower_layer_ptr;
    }
 
    constexpr void SetHigherLayer(HigherLayerhigher_layer_ptrnoexcept
    {
        m_higher_layer_ptr = higher_layer_ptr;
    }
 
    constexpr void Down() noexcept
    {
        PRINT_FUNCTION;
        TransitionFromDownToUp();
    }
 
    constexpr void TransitionFromDownToUp() noexcept
    {
        PRINT_FUNCTION;
        Up();
    }
 
    constexpr void Up() noexcept
    {
        PRINT_FUNCTION;
        m_higher_layer_ptr->Up();
    }
 
    constexpr void DoSomething() noexcept
    {
        PRINT_FUNCTION;
    }
};
 
template <typename Thistypename LowerLayertypename HigherLayer>
class Layer2
{
private:
    LowerLayer*  m_lower_layer_ptr{ nullptr };
    HigherLayer* m_higher_layer_ptr{ nullptr };
    int          m_value{ 123 };
 
public:
    Layer2()                          = default;
    Layer2(const Layer2&)             = delete;
    Layer2(Layer2&&)                  = delete;
    ~Layer2()                         = default;
    Layer2operator =(const Layer2&) = delete;
    Layer2operator =(Layer2&&)      = delete;
 
    constexpr void SetLowerLayer(LowerLayerlower_layer_ptrnoexcept
    {
        m_lower_layer_ptr = lower_layer_ptr;
    }
 
    constexpr void SetHigherLayer(HigherLayerhigher_layer_ptrnoexcept
    {
        m_higher_layer_ptr = higher_layer_ptr;
    }
 
    constexpr void Down() noexcept
    {
        PRINT_FUNCTION;
        m_lower_layer_ptr->Down();
    }
 
    constexpr void Up() noexcept
    {
        PRINT_FUNCTION;
        m_higher_layer_ptr->Up();
    }
 
    constexpr int Value() const noexcept
    {
        //PRINT_FUNCTION;
        return m_value;
    }
 
    constexpr void Value(int valuenoexcept
    {
        //PRINT_FUNCTION;
        m_value = value;
    }
 
    constexpr void DoSomething() noexcept
    {
        PRINT_FUNCTION;
    }
 
};
 
template <typename Thistypename LowerLayertypename HigherLayer>
class Layer3
{
private:
    LowerLayer*  m_lower_layer_ptr{ nullptr };
    HigherLayer* m_higher_layer_ptr{ nullptr };
 
public:
    Layer3()                          = default;
    Layer3(const Layer3&)             = delete;
    Layer3(Layer3&&)                  = delete;
    ~Layer3()                         = default;
    Layer3operator =(const Layer3&) = delete;
    Layer3operator =(Layer3&&)      = delete;
 
    constexpr void SetLowerLayer(LowerLayerlower_layer_ptrnoexcept
    {
        m_lower_layer_ptr = lower_layer_ptr;
    }
 
    constexpr void SetHigherLayer(HigherLayerhigher_layer_ptrnoexcept
    {
        m_higher_layer_ptr = higher_layer_ptr;
    }
 
    constexpr void Down() noexcept
    {
        PRINT_FUNCTION;
        m_lower_layer_ptr->Down();
    }
 
    constexpr void Up() noexcept
    {
        PRINT_FUNCTION;
    }
 
    constexpr int GetLayer2Value() const noexcept
    {
        //PRINT_FUNCTION;
        return m_lower_layer_ptr->Value();
    }
 
    constexpr void SetLayer2Value(int valuenoexcept
    {
        //PRINT_FUNCTION;
        return m_lower_layer_ptr->Value(value);
    }
 
    constexpr void DoSomething() noexcept
    {
        PRINT_FUNCTION;
    }
};
 
int main()
{
    using ILayers = Layers<Layer1Layer2Layer3>;
 
    auto layer1_ptr{ std::make_unique<ILayers::ILayer1>() };
    auto layer2_ptr{ std::make_unique<ILayers::ILayer2>() };
    auto layer3_ptr{ std::make_unique<ILayers::ILayer3>() };
 
    layer1_ptr->SetLowerLayer(nullptr);
    layer1_ptr->SetHigherLayer(layer2_ptr.get());
 
    layer2_ptr->SetLowerLayer(layer1_ptr.get());
    layer2_ptr->SetHigherLayer(layer3_ptr.get());
 
    layer3_ptr->SetLowerLayer(layer2_ptr.get());
    layer3_ptr->SetHigherLayer(nullptr);
 
    layer3_ptr->Down();
 
    std::wcout << layer3_ptr->GetLayer2Value() << '\n';
 
    int value{ 0 };
    std::wcin >> value;
 
    layer3_ptr->SetLayer2Value(value);
 
    std::wcout << layer3_ptr->GetLayer2Value() << '\n';
 
    using Layer1PtrType = std::decay_t<decltype(layer1_ptr)>;
    using Layer2PtrType = std::decay_t<decltype(layer2_ptr)>;
    using Layer3PtrType = std::decay_t<decltype(layer3_ptr)>;
    using LayerPtrVariantType = std::variant
    <
        Layer1PtrType,
        Layer2PtrType,
        Layer3PtrType
    >;
    std::vector<LayerPtrVariantType> layer_ptr_variants{};
    layer_ptr_variants.emplace_back(std::move(layer1_ptr));
    layer_ptr_variants.emplace_back(std::move(layer2_ptr));
    layer_ptr_variants.emplace_back(std::move(layer3_ptr));
    for (auto&& layer_ptr_variant : layer_ptr_variants)
    {
        std::visit
        (
            [](auto&& layer_ptrnoexcept
            {
                layer_ptr->DoSomething();
            },
            layer_ptr_variant
        );
    }
}

When the code above is compiled with g++ and run, it produces the following output (assuming an input of “456”):

Down
Down
Down
TransitionFromDownToUp
Up
Up
Up
123
456
456
DoSomething
DoSomething
DoSomething

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.