1

I have some types defined by the values of an enumerator, which represent the type of data read in from a file. I wish to do a different processing workflow based on the type of data , but it results in a lot of code duplication:

#include <iostream>

enum dataType {
    type1,
    type2,
    type3
};

struct File {
    File(dataType type):type{type}{};
    dataType type;
};

void process_file(File file)
{
    if(file.type == dataType::type1){ std::cout << "Do work A" << std::endl; };
    if(file.type == dataType::type2){ std::cout << "Do work B" << std::endl; };
    if(file.type == dataType::type3){ std::cout << "Do work C" << std::endl; }; 
}


int main(){
    File file(dataType::type2);
    process_file(file);
    return 0;
}

My main problem is with having to check the value via "if" or a switch statement. Imagine there being 50 types instead of just 3 and it becomes quite a chore and error prone to check every single one.

Does anyone know of a way to deal with this? Template code is the obvious thing to try but I'm stuck using the enumerator to determine the type, so I didn't think template code was possible here, at least the attempts I've made have not been successful.

Brock Hargreaves
  • 842
  • 6
  • 12
  • 2
    What's feasible let alone useful is going to depend on what "do work" means for you. A `map` may be what you want, or it may not be... no matter what you do, though, _somewhere_ you need to write out what each value means. You'll need to be more precise before we can offer a free consultation on your program's design! – Lightness Races in Orbit Mar 13 '18 at 19:13
  • how about using switch or utilizing polymorphism (that is making vitual `Do_Work` method and implementing it for each data type)? – user7860670 Mar 13 '18 at 19:14
  • You might look into using `std::variant` (or `boost::variant` if you need to support pre-C++17 compilers) instead of rolling your own version, which is what it looks like you might be starting to do here. If you use that, then you can define a visitor class accepting different types, with possibly even some templatization of the visitor's `operator()`. – Daniel Schepler Mar 13 '18 at 19:26
  • @LightnessRacesinOrbit A map works for me, thanks. I tried to come up with a simple example so that any suggestions put forward would benefit others and not only myself. I guess I've failed at finding a balance. – Brock Hargreaves Mar 13 '18 at 20:38
  • @DanielSchepler Ah that seems really neat, I haven't used variants before so I'll have to read a little bit before I know whether I can apply them here. But perhaps they'll help me with a future problem! – Brock Hargreaves Mar 13 '18 at 20:38
  • @VTT I've accepted liliscent's solution, though I think polymorphism is overkill in my case and a map that LightnessRacesInOrbit suggested is enough. – Brock Hargreaves Mar 13 '18 at 20:38
  • @BrockHargreaves: Finding that balance is not always easy, to be sure. – Lightness Races in Orbit Mar 13 '18 at 20:41

3 Answers3

2

A typical way to get rid of switch is inheritance and virtual function:

struct File {
    virtual ~File() = default;
    virtual void process() = 0;
};

struct Type1File : public File {
    void process() override { std::cout << "Do work A" << std::endl; };
};
struct Type2File : public File {
    void process() override { std::cout << "Do work B" << std::endl; };
};

int main(){
    std::unique_ptr<File> file = std::make_unique<Type1File>();

    file->process();
    return 0;
}
llllllllll
  • 16,169
  • 4
  • 31
  • 54
1

How about injecting a SomeWorker object into the file class instead of having a type data member?

class SomeWorker
{
    ...
    public:
        virtual void DoWork() = 0;
};

class SomeWorker1 : public SomeWorker
{
    ...
    public:
        void DoWork() override { std::cout << "Do work A" << std::endl;}
};

class SomeWorker2 : public SomeWorker
{
    ...
    public:
        void DoWork() override { std::cout << "Do work B" << std::endl;}
};

...

struct File {
    File(SomeWorker worker):someWorker{worker}{};
    SomeWorker someWorker;
};

int main(){
    SomeWorker2 someWorker;
    File file(someWorker);
    file.someWorker.DoWork();
    return 0;
}

Obviously, the code is not complete and there are virtual destructors to add and things to improve, but you get the idea...

Jorge Y.
  • 1,123
  • 1
  • 9
  • 16
  • What I did for a similar situation with hundreds of key/function pairs was to have a struct of key and pointer to function items, have a vector of those pairs, sorted by key, from which I could get a pointer to the worker function given the key. Since you are using `enum` values, you could index into such a vector. – vacuumhead Mar 13 '18 at 20:36
  • Sure, it would work too. I prefer using subclasses though, either through injection or through inheritance (as in the accepted answer), as I find myself often adding more and more related functionality per key/type, so it makes sense to group all that functionality in the subclasses. – Jorge Y. Mar 13 '18 at 20:50
1

You can do it passing the dataType as a template parameter.

#include <iostream>

enum class dataType {
    type1,
    type2,
    type3
};

template <dataType T>
struct File {};

void process_file(File<dataType::type1> file) {
    std::cout << "Do work A" << std::endl;
}

void process_file(File<dataType::type2> file) {
    std::cout << "Do work B" << std::endl;
}

void process_file(File<dataType::type3> file) {
    std::cout << "Do work C" << std::endl;
}


int main() {
    File<dataType::type1> file1;
    File<dataType::type2> file2;
    File<dataType::type3> file3;

    process_file(file1);
    process_file(file2);
    process_file(file3);
    return 0;
}

However you then also need to accommodate the fact that File is a template, so passing it to other functions ect. is not as easy anymore. You can either change all functions dealing with File to a template aswell, or give all the File variations a common base class.

The other answers seem easier and more to the point in this case to me. Mostly posted this since you mentioned it in your question.

super
  • 12,335
  • 2
  • 19
  • 29
  • This code is essentially function overloading, specialization is redundant. See [here](https://stackoverflow.com/questions/7108033/template-specialization-vs-function-overloading) why overloading is preferred. – llllllllll Mar 13 '18 at 20:15
  • @liliscent You're absolutely right. That's was silly. :-) – super Mar 13 '18 at 20:24