2

I would like to create an application in which I can define list of typed questions, then iterate over this list and ask user to enter answers, storing them in another list, then go over answers, validate them and provide a result.

My current naive approach is something like this:

class Question {
  std::string message;
public:
  Question(const std::string& msg) : message{msg} {}
  std::string& getQuestion(void) const {
    return this->message;
  }
  virtual ~Question(void) = 0;
};

Question::~Question(void) {}

template<class AnswerType>
class Prompt : public Question {
   Prompt(std::string& msg) : Question{msg} {}
   virtual ~Prompt(void) {}
};

class Answer<class T> {
  T answ;
public:
  Answer(const T& answer) : answ{answer} {}
  T getAnswer(void) const {
    return this->answ;
  }
};

and I would like to do something like:

std::list< const Question* > whatToAsk{
  new Prompt<int>{"Your age"},
  new Prompt<std::string>{"Your name"},
  new Prompt<float>{"Your weight"}
};

for(auto q in whatToAsk) {
  Answer< "derived q template parameter" > a{};
  std::cout << q->getQuestion() << ": ";
  std::cin >> a;
  // ... to be continued ...
}

storing questions (Prompt< T >) inside of std::list< const Question* >.

But problematic part for me is that I've to use downcasting (with runtime checking), virtual functions (runtime polymorphism) or double dispatch (again with runtime overhead).

My concern is that types are known during compilation, because list of questions will be hardcoded in the source code and I want to avoid runtime overhead and achieve compile-time static polymorphism.

How can I achieve this? Some kind of traits maybe?

GeekMagus
  • 210
  • 2
  • 11
  • You want to create an object `a` whose type will be known at runtime. I don't think that is possible. I also don't think it would be possible to create a different type inside a for loop body for every iteration unless the types are part of the same inheritance chain (runtime polymorphism). – rozina Apr 04 '16 at 11:58

4 Answers4

2

If you don't want to use virtual functions/dynamic_casts you should

  1. Make each question of a different type.
  2. Store them in a tuple, not in a list.
  3. Iterate using a special function (forEachArgument -- it's googleable).

It is much simpler to do it with virtual functions and unless you wil have thousands of questions the runtime overhead is negligible.

grisumbras
  • 850
  • 2
  • 6
  • 11
  • +1 for tuple, completely forgot about it. This will be my plan B, because I want to give a try to variadic templates first. – GeekMagus Apr 10 '16 at 10:05
1

I'm not an expert but maybe you can use c++11 variadic templates that would allow you to have an array of different types, and thus would not cause any overhead due to downcast.

this link may interest you Create static array with variadic templates

Community
  • 1
  • 1
Seb Maire
  • 132
  • 8
  • Brilliant. I've totally forgot about the power of variadic templates. Will give it a try and report back my progress. – GeekMagus Apr 10 '16 at 10:20
1

A few sparse notes, more than a reply

Add the answer handling stuff section a member function of question. This way you can know the type of question. Something like

void ask()
{
    Answer<T> answer ; 
    std::cint >> a ;
    ....
}

Use std::shared_ptr instead of plain pointers.

Probably an idiom known as virtual constructor as question factory can be helpful for question building

Something like

Question *make_question(int type)
{
   switch (type)
   {
      case 0: return new Prompt<int>() ; 
      case 1: return new Prompt<std::string>() ;
      ...
    }
}
HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
marom
  • 5,064
  • 10
  • 14
  • This is what I'm actually trying to avoid. This design decision is not "future-proof". Any time you want to have a new type you have to add it to factory. – GeekMagus Apr 10 '16 at 10:15
1

If you are using c++14 you could do something like:

#include <iostream>

template <class FirstQuestion, class... OtherQuestions>
struct QuestionList {
   template <class Functor>
   void foreach(Functor &&functor) {
      functor(FirstQuestion());
      QuestionList<OtherQuestions...> oql;
      oql.foreach(functor);
   }
};

template <class FirstQuestion>
struct QuestionList<FirstQuestion> {
   template <class Functor>
   void foreach(Functor &&functor) {
      functor(FirstQuestion());
   }
};



template <class AnswerType, const char *QuestionString>
struct Question {
   static AnswerType answer;
   static void print_question() {
      std::cout << QuestionString << std::endl;
   }
   static void get_answer() {
      std::cin >> answer;
   }
};

template <class AnswerType, const char *QuestionString>
AnswerType Question<AnswerType, QuestionString>::answer;

constexpr char questionstrings1[] = "lorem";
constexpr char questionstrings2[] = "ipsum";

int main() {
  QuestionList<Question<int, questionstrings1>, Question<float, questionstrings2> > a;
  a.foreach([](auto x){ x.print_question(); x.get_answer(); });
}

To access the answer in a loop you can simply:

a.foreach([](auto x){ /*doing something with x.answer*/ };

PS have in mind that prompting user for an answer kills potential efficiency of the non-runtime polymorhism...

W.F.
  • 13,888
  • 2
  • 34
  • 81
  • Thanks for the hint. Unfortunately, I do not have an access to C++14 compiler at the moment and auto type for lambda functions is not available, but I will definitely keep this in mind for the future! – GeekMagus Apr 10 '16 at 10:41