1

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?

betrunken
  • 114
  • 7
  • You could have a pure virtual `begin()` and `end()` function that return iterators, and then have the `foo()` routine call those virtual functions to get the iterators. – Eljay May 04 '21 at 12:55
  • 2
    Possibly a case for a class template? But, if the only differences between the types is the dimension of the `std:array`, that may be overkill. Can you add a bit more clarity? – Adrian Mole May 04 '21 at 12:56
  • Does `foo` use the `constexpr`? else `template int foo(const Container&, float);` or `int foo(std::span, float);` seems to do the job. – Jarod42 May 04 '21 at 13:03
  • @AdrianMole It's just a toy example (possibly not a very good one). My main point was can this "same behavior, different data" thing be implemented such that the data is stored on the stack rather than the heap? – betrunken May 04 '21 at 13:10
  • @Jarod42 It doesn't, but I don't think the function template solves the core of my dilemma. Or if it does I'm not sure how. Could you elaborate? – betrunken May 04 '21 at 13:12
  • @betrunken OK - but, if you want stack/heap differentiation, I don't get why you have the data declared as `static` and `constexpr`. – Adrian Mole May 04 '21 at 13:12
  • 2
    @betrunken Would that be a solution to your problem? https://godbolt.org/z/ovhnMvnx3 – m88 May 04 '21 at 13:15
  • @m88 I think it is! Thanks very much! – betrunken May 04 '21 at 13:21
  • 1
    @betrunken please note, that the beauty of m88 solution is that your foo member function has been substitute with a non-member non-friend function, I suggest you to read this question as well https://stackoverflow.com/questions/5989734/effective-c-item-23-prefer-non-member-non-friend-functions-to-member-functions – Alessandro Teruzzi May 04 '21 at 13:30

2 Answers2

1

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;
}
alex_noname
  • 26,459
  • 5
  • 69
  • 86
1

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.

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83