Skip to content

Instantly share code, notes, and snippets.

@dboyliao
Last active March 16, 2022 09:33
Show Gist options
  • Select an option

  • Save dboyliao/95f827d584df1c8b66cb8d0fe55b6725 to your computer and use it in GitHub Desktop.

Select an option

Save dboyliao/95f827d584df1c8b66cb8d0fe55b6725 to your computer and use it in GitHub Desktop.

Revisions

  1. dboyliao revised this gist Mar 16, 2022. 1 changed file with 5 additions and 1 deletion.
    6 changes: 5 additions & 1 deletion README.md
    Original file line number Diff line number Diff line change
    @@ -4,4 +4,8 @@
    $ mkdir -p build
    $ cmake -B build -S .
    $ cmake --build build
    ```
    ```

    # References

    - https://stackoverflow.com/questions/149336/practical-uses-for-the-curiously-recurring-template-pattern
  2. dboyliao revised this gist Mar 16, 2022. 2 changed files with 7 additions and 8 deletions.
    4 changes: 2 additions & 2 deletions private.cpp
    Original file line number Diff line number Diff line change
    @@ -3,6 +3,6 @@

    #include "public.hpp"

    float CartesianPoint::dist_impl() const { return std::hypot(v0(), v1()); }
    float CartesianPoint::dist() const { return std::hypot(v0(), v1()); }

    float PolarPoint::dist_impl() const { return std::abs(v0()); }
    float PolarPoint::dist() const { return std::abs(v0()); }
    11 changes: 5 additions & 6 deletions public.hpp
    Original file line number Diff line number Diff line change
    @@ -12,12 +12,13 @@ class PointBase {
    float const &v1() const { return m_v1; }
    float &v1() { return m_v1; }
    float dist() const {
    static_assert(&Derived::dist != &PointBase::dist,
    "derived class must overwrite dist");
    auto &obj = derived();
    return obj.dist_impl();
    return obj.dist();
    }

    private:
    virtual float dist_impl() const = 0;
    Derived const &derived() const { return *static_cast<Derived const *>(this); }
    float m_v0, m_v1;

    @@ -37,7 +38,7 @@ class CartesianPoint : public PointBase<CartesianPoint> {
    using PointBase<CartesianPoint>::PointBase;

    private:
    float dist_impl() const;
    float dist() const;
    friend class PointBase<CartesianPoint>;
    };

    @@ -46,7 +47,7 @@ class PolarPoint : public PointBase<PolarPoint> {
    using PointBase<PolarPoint>::PointBase;

    private:
    float dist_impl() const;
    float dist() const;
    friend class PointBase<PolarPoint>;
    };

    @@ -55,7 +56,6 @@ class BadPoint : public PointBase<BadPoint> {
    using PointBase<BadPoint>::PointBase;

    private:
    float dist_impl() const { return -1.0; }
    friend class PointBase<BadPoint>;
    };

    @@ -64,6 +64,5 @@ class BadBadPoint : public PointBase<BadPoint> {
    using PointBase<BadPoint>::PointBase;

    private:
    float dist_impl() const { return -1.0; }
    friend class PointBase<BadPoint>;
    };
  3. dboyliao revised this gist Mar 16, 2022. 2 changed files with 1 addition and 2 deletions.
    1 change: 1 addition & 0 deletions .gitignore
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,4 @@
    build
    lib
    bin
    .vscode
    2 changes: 0 additions & 2 deletions crtp.cpp
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,3 @@


    #include <cstdint>
    #include <utility>

  4. dboyliao revised this gist Mar 16, 2022. 2 changed files with 9 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions CMakeLists.txt
    Original file line number Diff line number Diff line change
    @@ -3,9 +3,9 @@ project(crtp_example LANGUAGES CXX)
    set(CMAKE_CXX_STANDARD 11)
    set(CMAKE_VERBOSE_MAKEFILE ON)

    add_library(public_lib SHARED src/private.cpp)
    add_library(public_lib SHARED private.cpp)
    target_include_directories(public_lib
    PUBLIC include
    PUBLIC ${CMAKE_SOURCE_DIR}
    )
    set_target_properties(public_lib
    PROPERTIES
    7 changes: 7 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,7 @@
    # Build

    ```bash
    $ mkdir -p build
    $ cmake -B build -S .
    $ cmake --build build
    ```
  5. dboyliao revised this gist Mar 16, 2022. 2 changed files with 0 additions and 0 deletions.
    File renamed without changes.
    File renamed without changes.
  6. dboyliao revised this gist Mar 16, 2022. 5 changed files with 103 additions and 64 deletions.
    3 changes: 3 additions & 0 deletions .gitignore
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,3 @@
    build
    lib
    bin
    20 changes: 20 additions & 0 deletions CMakeLists.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,20 @@
    cmake_minimum_required(VERSION 3.8)
    project(crtp_example LANGUAGES CXX)
    set(CMAKE_CXX_STANDARD 11)
    set(CMAKE_VERBOSE_MAKEFILE ON)

    add_library(public_lib SHARED src/private.cpp)
    target_include_directories(public_lib
    PUBLIC include
    )
    set_target_properties(public_lib
    PROPERTIES
    LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib
    )

    add_executable(${PROJECT_NAME} ${CMAKE_SOURCE_DIR}/crtp.cpp)
    target_link_libraries(${PROJECT_NAME} public_lib)
    set_target_properties(${PROJECT_NAME}
    PROPERTIES
    RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin
    )
    67 changes: 3 additions & 64 deletions crtp.cpp
    Original file line number Diff line number Diff line change
    @@ -1,70 +1,9 @@
    #include <algorithm>
    #include <cmath>
    #include <cstdint>
    #include <iostream>
    #include <utility>

    template <class Derived>
    class PointBase {
    public:
    constexpr static const double PI = 3.14159265358979323846;
    PointBase(float v0, float v1) : m_v0(v0), m_v1(v1) {
    std::cout << "PointBase: " << this << std::endl;
    }
    float const &v0() const { return m_v0; }
    float &v0() { return m_v0; }
    float const &v1() const { return m_v1; }
    float &v1() { return m_v1; }
    float dist() const {
    // Prevent the derived class from working if it doesn't define dist(),
    // just like what a pure virtual function does.
    static_assert(&PointBase<Derived>::dist != &Derived::dist,
    "derived class must define dist()");
    return static_cast<const Derived *>(this)->dist();
    }

    private:
    Derived const &derived() const { return *static_cast<Derived const *>(this); }
    float m_v0, m_v1;

    private:
    // safe guard for invalid inheritance
    PointBase() {}
    friend Derived;
    }; /* end class PointBase */

    template <typename Derived>
    float use_dist(const PointBase<Derived> &pt) {
    return pt.dist();
    }

    class CartesianPoint : public PointBase<CartesianPoint> {
    public:
    using PointBase<CartesianPoint>::PointBase;
    float dist() const { return std::hypot(v0(), v1()); }
    }; /* end class CartesianPoint */

    class PolarPoint : public PointBase<PolarPoint> {
    public:
    using PointBase<PolarPoint>::PointBase;
    float dist() const { return std::abs(v0()); }
    }; /* end class PolarPoint */

    class BadPoint : public PointBase<BadPoint> {
    public:
    using PointBase<BadPoint>::PointBase;
    // Intentionally omit dist().
    /*float dist() const
    {
    return 0.0;
    }*/
    }; /* end class BadPoint */
    #include <cstdint>
    #include <utility>

    // intentionally iheritant from wrong base class
    class BadBadPoint : public PointBase<BadPoint> {
    public:
    using PointBase<BadPoint>::PointBase;
    };
    #include "public.hpp"

    int main(int, char **) {
    std::cout << "sizeof(CartesianPoint) = " << sizeof(CartesianPoint)
    69 changes: 69 additions & 0 deletions include/public.hpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,69 @@
    #include <iostream>

    template <class Derived>
    class PointBase {
    public:
    constexpr static const double PI = 3.14159265358979323846;
    PointBase(float v0, float v1) : m_v0(v0), m_v1(v1) {
    std::cout << "PointBase: " << this << std::endl;
    }
    float const &v0() const { return m_v0; }
    float &v0() { return m_v0; }
    float const &v1() const { return m_v1; }
    float &v1() { return m_v1; }
    float dist() const {
    auto &obj = derived();
    return obj.dist_impl();
    }

    private:
    virtual float dist_impl() const = 0;
    Derived const &derived() const { return *static_cast<Derived const *>(this); }
    float m_v0, m_v1;

    private:
    // safe guard for invalid inheritance
    PointBase() {}
    friend Derived;
    }; /* end class PointBase */

    template <typename Derived>
    float use_dist(const PointBase<Derived> &pt) {
    return pt.dist();
    }

    class CartesianPoint : public PointBase<CartesianPoint> {
    public:
    using PointBase<CartesianPoint>::PointBase;

    private:
    float dist_impl() const;
    friend class PointBase<CartesianPoint>;
    };

    class PolarPoint : public PointBase<PolarPoint> {
    public:
    using PointBase<PolarPoint>::PointBase;

    private:
    float dist_impl() const;
    friend class PointBase<PolarPoint>;
    };

    class BadPoint : public PointBase<BadPoint> {
    public:
    using PointBase<BadPoint>::PointBase;

    private:
    float dist_impl() const { return -1.0; }
    friend class PointBase<BadPoint>;
    };

    class BadBadPoint : public PointBase<BadPoint> {
    public:
    using PointBase<BadPoint>::PointBase;

    private:
    float dist_impl() const { return -1.0; }
    friend class PointBase<BadPoint>;
    };
    8 changes: 8 additions & 0 deletions src/private.cpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,8 @@
    #include <algorithm>
    #include <cmath>

    #include "public.hpp"

    float CartesianPoint::dist_impl() const { return std::hypot(v0(), v1()); }

    float PolarPoint::dist_impl() const { return std::abs(v0()); }
  7. dboyliao created this gist Mar 16, 2022.
    90 changes: 90 additions & 0 deletions crtp.cpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,90 @@
    #include <algorithm>
    #include <cmath>
    #include <cstdint>
    #include <iostream>
    #include <utility>

    template <class Derived>
    class PointBase {
    public:
    constexpr static const double PI = 3.14159265358979323846;
    PointBase(float v0, float v1) : m_v0(v0), m_v1(v1) {
    std::cout << "PointBase: " << this << std::endl;
    }
    float const &v0() const { return m_v0; }
    float &v0() { return m_v0; }
    float const &v1() const { return m_v1; }
    float &v1() { return m_v1; }
    float dist() const {
    // Prevent the derived class from working if it doesn't define dist(),
    // just like what a pure virtual function does.
    static_assert(&PointBase<Derived>::dist != &Derived::dist,
    "derived class must define dist()");
    return static_cast<const Derived *>(this)->dist();
    }

    private:
    Derived const &derived() const { return *static_cast<Derived const *>(this); }
    float m_v0, m_v1;

    private:
    // safe guard for invalid inheritance
    PointBase() {}
    friend Derived;
    }; /* end class PointBase */

    template <typename Derived>
    float use_dist(const PointBase<Derived> &pt) {
    return pt.dist();
    }

    class CartesianPoint : public PointBase<CartesianPoint> {
    public:
    using PointBase<CartesianPoint>::PointBase;
    float dist() const { return std::hypot(v0(), v1()); }
    }; /* end class CartesianPoint */

    class PolarPoint : public PointBase<PolarPoint> {
    public:
    using PointBase<PolarPoint>::PointBase;
    float dist() const { return std::abs(v0()); }
    }; /* end class PolarPoint */

    class BadPoint : public PointBase<BadPoint> {
    public:
    using PointBase<BadPoint>::PointBase;
    // Intentionally omit dist().
    /*float dist() const
    {
    return 0.0;
    }*/
    }; /* end class BadPoint */

    // intentionally iheritant from wrong base class
    class BadBadPoint : public PointBase<BadPoint> {
    public:
    using PointBase<BadPoint>::PointBase;
    };

    int main(int, char **) {
    std::cout << "sizeof(CartesianPoint) = " << sizeof(CartesianPoint)
    << std::endl;
    std::cout << "sizeof(PolarPoint) = " << sizeof(PolarPoint) << std::endl;

    CartesianPoint cpt(1, 1);
    PolarPoint ppt(1, PolarPoint::PI / 4);

    std::cout << "CartesianPoint(1,1)::dist() = " << use_dist(cpt) << std::endl;
    std::cout << "PolarPoint(1, pi/4)::dist() = " << use_dist(ppt) << std::endl;

    // This won't compile
    // error: static_assert failed "derived class must define dist()"
    // BadPoint bp;
    // use_dist(bp);

    // This won't compile
    // since the default constructor is not accessible to BadBadPoint (due to
    // incorrect usage of CRTP)
    // BadBadPoint bbp;
    return 0;
    }