0

I have a non-type template class template<std::size_t N> Derived<N> which derives from some non-template base class Base:

class Base
{
public:
    double some_value;

    // Some methods and variables that do not depend on N
    // I wish to keep these in a non-templated class

    virtual const size_t get_N() = 0;
    virtual ~Base() = default;
    Base(double value): some_value {value} {};
};

template <std::size_t N>
class Derived: public Base
{
public:
    double some_other_value;

    // Some functions and variables, for which
    // the types and actions depend on N

    const size_t get_N() override
    {
        return N;
    }
    Derived(double value1, double value2): Base(value1), some_other_value {value2} {};
};

Now I have a function call_by_base(Base& my_base), which only uses member variables/functions declared in Base. The only exception to this, is a call to template<std::size_t N> void call_by_derived(Derived& my_derived). Since almost the entire function call_by_base is independent of the template parameter, I would prefer to keep this function non-templated.

I tried to achieve the above with implementations along the lines of:

template<std::size_t N>
void call_by_derived(Derived<N>& my_derived)
{
    std::cout << "in call_by_derived" << std::endl;
    // Methods that use the functions and variables in Derived.
}

void broken_call_by_base(Base& my_base)
{
    std::cout << "in call_by_base" << std::endl;

    // Manipulations independent of child
    // type Derived<N>

    auto derived = dynamic_cast<Derived<my_base.get_N()>&>(my_base);
    call_by_derived(derived);

    // Manipulations independent of child
    // type Derived<N>
}

When I try to compile this code, I get error: expression ‘Base::get_N’ is not a constant-expression. I tried to resolve this error by trying to change different thing, both in my Base and Derived class. This was all without success.

I managed to get the following alternative to work:

void working_call_by_base(Base& my_base)
{
    std::cout << "in call_by_base" << std::endl;

    if(my_base.get_N()==2)
    {
        auto derived = dynamic_cast<Derived<2>&>(my_base);
        call_by_derived(derived);
    }
    if(my_base.get_N()==3)
    {
        auto derived = dynamic_cast<Derived<3>&>(my_base);
        call_by_derived(derived);
    }
}

This is however very tedious, especially when N can take on many more values. Is there any way to get a function along the lines of broken_call_by_base working? That is: How can I downcast a non-template Base to a non-type template Derived<N>?

ps. Only object of the Derived<N> type will be created. This is the code in main to test:

int main()
{
    Derived<3> test(1.0,2.0);
        working_call_by_base(test);
        broken_call_by_base(test);
        call_by_derived(test);
}
JorenV
  • 373
  • 2
  • 10
  • Unfortunately, except if you do a big switch and implement all options, it's not going to work. It's not very good to have a base method using the derived class type... So probably you want to have some virtual entry points that the derived class will implement properly. – Matthieu Brucher Nov 29 '18 at 16:25
  • The problem with that is that my `Derived` class contains some functions, for which the number of arguments depend on `N` (or equivalently on a `std::array r`. As far as I know, I can not make these function virtual members of my `Base`. – JorenV Nov 29 '18 at 16:33
  • 2
    Then you need to implement some kind of visitor pattern. – Matthieu Brucher Nov 29 '18 at 16:46
  • 1
    @MatthieuBrucher: Even if you are able to use a visitor here, you have somewhere to define a list of all possible callable derived classes. It is not important to say it is a list of cast operations inside a switch/case block or defining a std::variant< typelist!!!>, where the typelist has to be the complete set of all types which the code can work on. So yes, visitor seams to be an option, but it still needs a list of all possible options in any way. – Klaus Nov 29 '18 at 17:04

1 Answers1

1

It'll be best if you can use a virtual member function to avoid the if/else checks. If that is not an option for some reason, use of a callback/plugin mechanism is the most appropriate.

  1. You provide a mechanism in the Base-specific code base to allow other classes/functions/modules to register functions that are appropriate for the type they know about.

  2. In the Base-specific code, you keep track of the registered functions using a key that is appropriate for Base.

  3. In the Base-specific code, you check whether a function has been registered for the key. If it has, you call the function with the suitable arguments.

  4. In the derived class specific code, you can downcast to the appropriate class. If the downcast succeeds, in most cases it should, you proceed to use the derived class.

This pattern strictly adheres to the Open-Closed Principle and is one of my favorite coding patterns.

In your case, the key is N.

Here's an example program that demonstrates the concept.

#include <iostream>

// Base.hpp
// #pragma once

#include <cstdint>

class Base
{
   public:
      double some_value;

      // Some methods and variables that do not depend on N
      // I wish to keep these in a non-templated class

      virtual const size_t get_N() = 0;
      virtual ~Base() = default;
      Base(double value): some_value {value} {};

      typedef void (*CallbackFunctionType1)(Base& b);
      static void registerCallback(std::size_t N, CallbackFunctionType1 f);

};

void call_by_base(Base& my_base);

// Base.cpp
#include <map>

namespace BaseNS
{
   using CallbackFunctionType1Map = std::map<std::size_t, Base::CallbackFunctionType1>;

   CallbackFunctionType1Map& getCallbackFunctionType1Map()
   {
      static CallbackFunctionType1Map theMap;
      return theMap;
   }
}

void Base::registerCallback(std::size_t N, CallbackFunctionType1 f)
{
   BaseNS::CallbackFunctionType1Map& theMap = BaseNS::getCallbackFunctionType1Map();
   theMap[N] = f;
}

void call_by_base(Base& my_base)
{
   std::cout << "In call_by_base" << std::endl;
   BaseNS::CallbackFunctionType1Map& theMap = BaseNS::getCallbackFunctionType1Map();
   BaseNS::CallbackFunctionType1Map::iterator iter = theMap.find(my_base.get_N());
   if ( iter != theMap.end() )
   {
      iter->second(my_base);
   }
}

// Derived.hpp
// #pragma once

template <std::size_t N>
class Derived: public Base
{
   public:

      double some_other_value;

      // Some functions and variables, for which
      // the types and actions depend on N

      const size_t get_N() override
      {
         return N;
      }

      Derived(double value1, double value2): Base(value1), some_other_value {value2} {};
};

// Derived.cpp
// Register call back functions for Derived.

namespace DerivedNS
{
   template <std::size_t N>
      void call_by_derived(Derived<N>& derived)
      {
         std::cout << "In call_by_derived<" << N << ">" << std::endl;
         // Use derived.
      }


   template <std::size_t N>
      void call_for_derived(Base& my_base)
      {
         Derived<N>* d_ptr = dynamic_cast<Derived<N>*>(&my_base);
         if ( d_ptr != nullptr )
         {
            call_by_derived(*d_ptr);
         }
         else
         {
            // Error.
         }
      }

   bool registerCallbackFunctions()
   {
      // Register callbacks for as many values of N as needed.
      Base::registerCallback(1, call_for_derived<1>);
      Base::registerCallback(2, call_for_derived<2>);
      Base::registerCallback(3, call_for_derived<3>);
      Base::registerCallback(4, call_for_derived<4>);
      Base::registerCallback(5, call_for_derived<5>);
      return true;
   }

   bool dummy = registerCallbackFunctions();
}

int main()
{
   Derived<1> d1(0, 0);
   Derived<2> d2(0, 0);
   Derived<10> d3(0, 0);
   call_by_base(d1);
   call_by_base(d2);
   call_by_base(d3); // Does not go to call_by_derived.
}

Output:

In call_by_base
In call_by_derived<1>
In call_by_base
In call_by_derived<2>
In call_by_base
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • Wow, I do not feel bad that I did not came up myself with such a solution! I was hoping for some nice template magic... this seems to work, but I feel it is a bit over the top. On the other hand, it might be the only solution... – JorenV Nov 30 '18 at 07:26