betrunken
betrunken

Reputation: 114

Data polymorphism pattern in C++

I'm not sure "data polymorphism" is a actually a thing, but it seemed like a reasonable expression for what I'm looking for: How would you represent multiple types that share a functionality, but operate on different static data?

Take for instance:

class A {
    static constexpr std::array<int, 5> m_static_data{1, 2, 3, 4, 5};
public:
    int foo(float x) { // do stuff based on m_static_data }
};

class B {
    static constexpr std::array<int, 3> m_static_data{42, 43, 44};
public:
    int foo(float x) { // do stuff based on m_static_data }
};

Where foo() is exactly the same between them.

One way would be to define foo in a base class, and a getter for the data in A and B:

class Base {
    virtual std::vector<int> get_data() = 0;
public:
    int foo(float x) { // do stuff based on get_data()'s returned value }
};

class A : Base{
    std::vector<int> get_data() override { return {1, 2, 3, 4, 5}; }
};

class B : Base{
    std::vector<int> get_data() override {return {42, 43, 44}; }
};

But then you would lose all the nice compile time checks and evaluations. Is there a better pattern for doing this sort of thing? And is there a name for it?

Upvotes: 1

Views: 154

Answers (2)

Adrian Mole
Adrian Mole

Reputation: 51915

How would you represent multiple types that share a functionality

This is really an archetypal definition of what a 'simple' class template is for; virtually all containers provided by the STL do just this: a std::vector<char> and std::vector<double> have a whole variety of member functions (and associated, free-standing functions) that do the same thing but on different data types.

but operate on different static data?

Declaring static data members of templated classes is a bit trickier, as is the use of constexpr members, but here's a short example of how you might go about doing so:

template<typename T>
class Base {
    static const T m_static_data;
public:
    T foo(float x) {
        return static_cast<T>(x * static_cast<float>(m_static_data));
    }
};

// Instantiate static data for "int" and "double" types ...
template<>
inline constexpr double Base<double>::m_static_data = 3.1415926536;
template<>
inline constexpr int Base<int>::m_static_data = 21;

int main()
{
    // Use "A" for a double and "B" for an int class object...
    Base<double> A;
    Base<int> B;

    std::cout << A.foo(1.0f) << std::endl;
    std::cout << B.foo(2.0f) << std::endl;

    return 0;
}

Note that the use of the inline keyword on the definitions requires a compiler conforming to at least C++17.

Upvotes: 1

alex_noname
alex_noname

Reputation: 32293

You can try using static polymorphism (CRTP idiom). It will allow you to use common functionality at compile time.

#include <iostream>
#include <memory>
#include <array>

template<typename Derived>
class Base {
public:
    void foo() {
        const auto& arr = static_cast<Derived*>(this)->m_static_data;
        std::copy(arr.begin(), arr.end(), std::ostream_iterator<int>(std::cout, " "));
        std::cout << std::endl;        
    }
};

class A : public Base<A> {
    friend class Base<A>;
    static constexpr std::array<int, 5> m_static_data{1, 2, 3, 4, 5};
};

class B: public Base<B> {
    friend class Base<B>;
    static constexpr std::array<int, 3> m_static_data{42, 43, 44};
};

int main()
{
    auto a = A();
    auto b = B();
    a.foo();
    b.foo();
    return 0;
}

Upvotes: 1

Related Questions