-1

I'm looking to try to write some generic code that can handle different data types. Once these data types are set, then it'll remain the same throughout the duration of the instance.

I think it'll be easier to show what I'm trying to do, rather than describe it.

helper.h

#include <iostream>
#include <type_traits>
#include <utility>
#include <string>
#include <sstream>
#include <stdexcept>
using namespace std;
template <typename T>
class helper
{
public:
    helper()
    {
      stringstream temp;
      if(is_same<T, short>::value)
      {
        temp << 1;
      }
      else if(is_same<T,long>::value)
      {
        temp << 1024;
      }
      else if(is_same<T, char*>::value)
      {
        temp << "Hello";
      }
      else if(is_same<T, string>::value)
      {
        temp << "Hello World";
      }
      else
      {
        throw invalid_argument("Error in helper: Unknown data type" + to_string(__LINE__) +  string(__FILE__));
      }

      temp >> data;
    }


    T getData()
    {
      return data;
    }

  protected:
    T data;
};

call.cpp

#include <iostream>
#include "helper.h"
using namespace std;

int main()
{
  helper<> my_helper;

  int data;
  cin >> data;
  switch(data)
  {
    case 1:
      my_helper = helper<short>;
      break;
    case 2:
      my_helper = helper<long>;
      break;
    case 3:
      my_helper = helper<char *>;
      break;
    default:
      my_helper = helper<string>;
      break;
  }
  cout << my_helper.getData() << endl;
  return 0;
}

Now, this won't compile because helper doesn't have a template argument, but is there a way that I can set the argument at a later point in time (like after there is user input as shown in the example)? Once the argument is set, there is not use case where it'll change. I know this is a trivial example where I could just do a cout in the switch-case, but this is the concept that I want to accomplish.

Unfortunately I'm stuck with C++11 and no boost libs, otherwise I think I could use std::any, I thought I could use void pointers, but then I'd have to specify what the datatype is when I call a reinterpret_cast.

If there is any additional information that I can provide or anything I can clear up, please let me know!

SailorCire
  • 548
  • 1
  • 7
  • 24
  • 5
    You're confusing runtime dispatching (through inheritance etc) with compile time template dispatching – Hatted Rooster Dec 14 '18 at 18:24
  • You need to do template dispatching and do the full code in the switch, basically. – Matthieu Brucher Dec 14 '18 at 18:25
  • That's what I was afraid of. So there isn't really a way to cut down on a ton of copying and pasting? – SailorCire Dec 14 '18 at 18:26
  • @SailorCire SFINAE and `std::enable_if` maybe? – πάντα ῥεῖ Dec 14 '18 at 18:27
  • Why don't you copy a `std::any` implementation of a C++17 compiler and use it with C++11? I would not rewrite `std::any`. I definitely could not write it better than a STL author. – Werner Henze Dec 14 '18 at 18:27
  • @WernerHenze didn't think about copying an implementation...isn't that going to require copying over stdlib? – SailorCire Dec 14 '18 at 18:29
  • @SailorCire I don't know, but I would give it a try. At least it is a good starting point and maybe you can easily cut off the STL specific stuff to port it. – Werner Henze Dec 14 '18 at 18:31
  • 1
    Does `getData()` have to return a `T`? If you only use case is printing and you can make that return a similar type (such as std::string). Then you can define a `helper_base` with `getData()` as a virtual member. Then you can create a specific instance of `helper` dynamically (ie via new). – Martin York Dec 14 '18 at 18:32
  • That might be the way to do it. Looks like @lvella has given it as an answer. – SailorCire Dec 14 '18 at 18:36

2 Answers2

2

helper<short> is one type, and helper<char *> is another, completely incompatible type. There is no type helper<> which can be assigned by both. For that you could use a base class:

class base {
   virtual ~base() = default;
};

template <typename T>
class helper: public base
{
    // your code
};


int main()
{
  std::unique_ptr<base> my_helper;

  int data;
  cin >> data;
  switch(data)
  {
    case 1:
      my_helper.reset(new helper<short>);
      break;
    case 2:
      my_helper.reset(new helper<long>);
      break;
    case 3:
      my_helper.reset(new helper<char *>);
      break;
    default:
      my_helper.reset(new helper<string>);
      break;
  }
  //cout << my_helper->getData() << endl;
  return 0;
}

But then I don't think there is a way to declare virtual T getData() inside base.

lvella
  • 12,754
  • 11
  • 54
  • 106
1

The answer to your question depends on the goals you are trying to achieve. So the first. Do not forget that templates and OOP are generally orthogonal. Each template specialization is a new type that is not related to others. Therefore, the only way to cast all specializations to the same type is to inherit from one base class. So

class AbstractHelper
{
public:
   virtual ~AbstractHelper() {}
};

template <typename T>
class helper : public AbstractHelper

Second. You need to answer the question - can the data of your template classes be processed by a single handler or is it impractical (based on performance considerations, implementation complexity, etc.) Suppose it can, for example as strings. In this case you need:

class AbstractHelper
{
public:
   virtual string getDataString() { return ""; }
   ...

template <typename T>
class helper : public AbstractHelper
{
public:
string getDataString()
   {
      return to_string(data);
   }

If this is not acceptable, you need to answer the question, is it possible to completely encapsulate the data processing? Suppose you can. Then

class AbstractHelper
{
public:
   virtual void printData() {  }
   ...

template <typename T>
class helper : public AbstractHelper
{
public:
void printData()
   {
      cout << data << endl;;
   }

Finally, the most difficult option is that you need different processing for all types and it cannot be encapsulated. Then you need to determine the source type and use dynamic type casting:

enum Type
{
   TYPE_SHORT,
   TYPE_LONG,
   TYPE_STRING
...

class AbstractHelper
{
protected:
   Type type_;
public:
   Type getType() { return type_; }
......

template <typename T>
class helper : public AbstractHelper
{
public:
   helper()
   {
      stringstream temp;
      if (is_same<T, short>::value)
      {
         temp << 1;
         type_ = TYPE_SHORT;
      }
      else if (is_same<T, long>::value)
      {
         temp << 1024;
         type_ = TYPE_LONG;
      }
      ...

And select the desired handler:

 switch (my_helper.getType())
   {
   case TYPE_SHORT:
      cout << dynamic_cast<helper<short>&>(my_helper).getData() << endl;
      break;
   case TYPE_LONG:
      cout << dynamic_cast<helper<long>&>(my_helper).getData() << endl;

Do not be confused by the amount of manual code - macros and templates can significantly reduce it.

Dmytro Dadyka
  • 2,208
  • 5
  • 18
  • 31