0

I have a class that has a constructor taking quite a few parameters

enum class FooType {FOO_A, FOO_B, FOO_C};

class Foo {
    Foo(const double a, const double b, .... const double n);
}

depending on the 'type', I only need a certain subset of the params. At the moment there are various constructors with different number of inputs, but some new types will be added so that the number of inputs is the same. I could just add the type to the constructor, have a long switch in it, but the params list is quite long.

Foo(FooType type, const double a, const double b, .... const double n) {
    if (type = FooType::FOO_A) {
        ...
    } else if ....

}

Doesn't seem too bad, but I also don't like having that long parameter list. Seems to easy to make typos that are a pain to debug. So I can a.) pass a structure in b.) do something else

and I am just curious about potential b solutions.

Is it possible to templateize this such that I could create a template constructor and call the constructor with something like

std::make_shared<Foo<FooType::FOO_A>>(a, b, c);

Note: I don't want to use inheritance since the rest of the class' functionality has absolutely no use/need for it.

chrise
  • 4,039
  • 3
  • 39
  • 74
  • and what's wrong with making different constructors? What if you have to do de-allocation and other cleanup in the destructor, but different types require different things, how would you go about that (Are you going to have a member variable to store the type?) – Omid CompSCI Jan 23 '18 at 04:30
  • It kind of seems like if they really need this many different parameters and such, they should just be split up into separate classes. And kind of have some sort of factory pattern in your code like createClassBasedOnType(Type t) { switch(t) { case A: New object type A; case B: New Object type B; etc...;Note: This isn't inheritance, just different classes to de-modularize things. – Omid CompSCI Jan 23 '18 at 04:38
  • @Omid. constructors of new types would require the same number of params as existing ones (and it s all doubles) so the signature would be identical – chrise Jan 23 '18 at 05:14
  • I am basically overriding some default params. I guess passing a structure with all the members is the easiest way to go, still. was mostly curious what other options are there – chrise Jan 23 '18 at 05:16
  • Note that template arguments to a constructor (or conversion operator) template must be deduced: `Foo` provides a template argument for a _class template_ `Foo`. – Davis Herring Jan 23 '18 at 05:42
  • @Davis, right... that makes sense. thanks for pointing out – chrise Jan 23 '18 at 05:50
  • @apple in my current case, it may be that for example I dont need to set the first params. so without named parameters defaults are not really helping – chrise Jan 23 '18 at 05:54
  • @chrise maybe you need [factory method](https://stackoverflow.com/a/5121500/5980430) – apple apple Jan 23 '18 at 05:54
  • I decided to just go with passing through a struct. seems there is no obvious other simple way to do it. your factory example is going in the same direction as well – chrise Jan 23 '18 at 05:57
  • Is `type` known at compile time? If not, how does the caller know which three arguments should be passed? In addition, `const double` in parameter will be adjusted to `double`, so your `const` is useless. – xskxzr Jan 23 '18 at 07:30

2 Answers2

3

This could be a use case for the named parameters idiom: http://www.cs.technion.ac.il/users/yechiel/c++-faq/named-parameter-idiom.html .

That would allow your constructor call to look like this:

File f = OpenFile("foo.txt")
           .readonly()
           .createIfNotExist()
           .appendWhenWriting()
           .blockSize(1024)
           .unbuffered()
           .exclusiveAccess();

Instead of the above example, you could have a helper class that contains all the named parameters and your class constructor would take an instance of the parameters class as its parameters.

This lets you freely pick the set of parameters that you initialize at construction time. If you want to enforce different subsets being initialized for the different types, then you should just write different constructor versions.

iBug
  • 35,554
  • 7
  • 89
  • 134
Sami Sallinen
  • 3,203
  • 12
  • 16
  • ah, that s actually quite neat. seems a bit overkill to write a member function for my case, but I quite like the idea and will probably get to a case at some point where this is the elegant way to go – chrise Jan 23 '18 at 05:12
  • Better idea for this would be a builder patter in my opinion. I will write a solution with that. – Maroš Beťko Jan 23 '18 at 08:49
1

Here is how you could make a templated constructor using a builder pattern:

class Foo {
    double a;
    int b;
    double c;
public:
    Foo(double a, int b, char c) {

    }
};

template <FooType Type>
class Builder { };

template <>
class Builder<FooType::FOO_A> {
    double _a;
public:
    Builder& a(double val) { _a = val; return *this; }
    Foo build() { return { _a, 0, 0 }; }
};

template <>
class Builder<FooType::FOO_B> {
    int _b;
public:
    Builder& b(int val) { _b = val; return *this; }
    Foo build() { return { 0.0, _b, 0 }; }
};

template <>
class Builder<FooType::FOO_C> {
    char _c;
public:
    Builder& c(char val) { _c = val; return *this; }
    Foo build() { return { 0.0, 0, _c }; }
};

The Builder class is templated as you wanted and the code you were executing in that if else statement, you can execute in builder's constructor or the build function on the instance of Foo you will be returning.

In the example a is relevant for FOO_A, b is relevant for FOO_B and c for FOO_C and other values get initialized to their default value.

This is how you would use it:

int main() {
    Foo testA = Builder<FooType::FOO_A>().a(12.5).build();
    Foo testB = Builder<FooType::FOO_B>().b(10).build();
    Foo testC = Builder<FooType::FOO_C>().c('x').build();
    return 0;
}

It is a pretty small example for a builder pattern, but from your example it seems like you are using much more arguments. To add another argument to any of the builder specializations in form Builder& typeName(Type val) { _typeName = val; return *this; } (it should return self reference so these funtions can be chained).

Maroš Beťko
  • 2,181
  • 2
  • 16
  • 42