I created the constexpr version of the Curiously Recurring Template Pattern and all seem to work as expected, except the destructor who "under normal circumstances" should be marked as virtual. As I can understand, virtual is the vital enemy of constexpr.
In my example I implemented two interfaces with no data-members. Is it correct in the general case (with data-members) to let virtual ~Crtp() = default;, virtual ~FeatureNamesInterface() = default; and virtual ~FeatureValuesInterface() = default; commented out and let the compiler to define the destructors? Does this approach have a memory leak? Is it a better approach to make them protected? Any other solution that work with constexpr would be welcome!
The interface code look like this
namespace lib
{
template <typename Derived, template<typename> class CrtpType>
struct Crtp
{
//virtual ~Crtp() = default;
[[nodiscard]] Derived& child() noexcept { return static_cast<Derived&>(*this); }
[[nodiscard]] constexpr Derived const& child() const noexcept { return static_cast<const Derived&>(*this); }
private:
constexpr Crtp() = default;
friend CrtpType<Derived>;
};
template<typename Derived>
struct FeatureNamesInterface : Crtp<Derived, FeatureNamesInterface>
{
constexpr FeatureNamesInterface() = default;
//virtual ~FeatureNamesInterface() = default;
[[nodiscard]] constexpr auto& GetFeatureNames() const noexcept { return Crtp<Derived, FeatureNamesInterface>::child().GetNames(); }
};
template<typename Derived>
struct FeatureDataInterface : Crtp<Derived, FeatureDataInterface>
{
constexpr FeatureDataInterface() = default;
//virtual ~FeatureValuesInterface() = default;
[[nodiscard]] constexpr auto GetFeatureData() const { return Crtp<Derived, FeatureDataInterface>::child()(); }
};
}
And the implementation of the two sample classes look like this
namespace impl
{
class ChildOne final : public lib::FeatureNamesInterface<ChildOne>, public lib::FeatureDataInterface<ChildOne>
{
static constexpr std::array mNames{"X"sv, "Y"sv, "Z"sv};
public:
constexpr ChildOne() : FeatureNamesInterface(), FeatureDataInterface() {}
~ChildOne() = default;
[[nodiscard]] constexpr auto& GetNames() const noexcept { return mNames; }
[[nodiscard]] constexpr auto operator()() const noexcept
{
std::array<std::pair<std::string_view, double>, mNames.size()> data;
double value = 1.0;
for (std::size_t i = 0; const auto& name : mNames)
data[i++] = {name, value++};
return data;
}
};
class ChildTwo final : public lib::FeatureNamesInterface<ChildTwo>, public lib::FeatureDataInterface<ChildTwo>
{
static constexpr std::array mNames{"A"sv, "B"sv, "C"sv, "D"sv, "E"sv, "F"sv};
public:
constexpr ChildTwo() : FeatureNamesInterface(), FeatureDataInterface() {}
~ChildTwo() = default;
[[nodiscard]] constexpr auto& GetNames() const noexcept { return mNames; }
[[nodiscard]] constexpr auto operator()() const noexcept
{
std::array<std::pair<std::string_view, double>, mNames.size()> data;
double value = 4.0;
for (std::size_t i = 0; const auto& name : mNames)
data[i++] = {name, value++};
return data;
}
};
}
The full example can be found here.