Constructor with variadic templates or something like that?

Suppose we have
1
2
3
4
5
6
7
8
9
10
enum Pet {Dog, Bird, Cat, ....};

struct PetStore {
  double dogWeight, dogHeight,  // and many more
  int catAge, // etc...
  Color birdColor, dogColor, catColor, //  etc...
  bool birdCanFly, dogWillBite, //  etc...
  std::string birdName, // etc...
  // ... 
}

I want a constructor that can be called like this:
1
2
3
4
5
PetStore p (
  Cat, 8, Black, 
  Dog, 12.5, 40.3, Brown, False, 
  Bird, Yellow, True, "Polly"
);

So then we will get
catAge = 8, catColor = Black,
dogWeight = 12.5, dogHeight = 40.3, dogColor = Brown, dogWillBite = False,
birdColor = Yellow, birdCanFly = true, birdName = "Polly".

I also want this to be independent of the order in which Cat, Dog, Bird, ...
is listed in the argument. In the case where all the data members are int,
I got it working using std::intializer_list<int> il and then iterating through it, i.e.
1
2
3
4
5
6
7
PetStore (const std::initializer_list<int>& il) {
for (auto it = il.cbegin(); it != il.cend(); ++it)
  switch (*it) {
    case Dog:  dogWeight = *(++it); dogHeight = *(++it); // etc...
    case Cat:  catWeight = *(++it);  // etc...
    // etc...
  }

The order independence of Cat, Dog, Bird, ... is there now. But of course, this method fails when the data members are of other types than just int. I also want the corresponding data members to be default-initialized if any of the pets are not listed in the argument.

So what is the constructor that I want? I suspect that it must be templated in variadic style. I also don't want to define new classes just for this reason, because such objects will not be used elsewhere in my program (there would be WAAAY too many such one-time-used classes anyway).
Last edited on
1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Pet
{
    //...
};
using PetStore = std::vector<Pet>;

//...

PetStore wherethepetsgo
{
    {Cat, 8, Black},
    {Dog, 12, 40, Brown, false},
    {Bird, Yellow, true, "Polly"}
};
It is more preferable to use polymorphism here, though.
Last edited on
So I have to define Derived classes Cat, Dog, Bird, etc... just to carry this out? I won't be needing thoses classes apart from this though.
Last edited on
If you don't want to use polymorphism, all the pets will need to have the same set of attributes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Pet
{
    //...
};
using PetStore = std::vector<Pet>;

//...

PetStore wherethepetsgo
{
    {Cat, 8, Black},
    {Dog, 12.5, 40.3, Brown, false},
    {Bird, Yellow, true, "Polly"}
}; 

Ok, following your idea above, I still don't see how {Cat, 8, Black} constructs a derived class object of Pet, {Dog, 12, 40, Brown, false} constructs another, etc... What are the data members of Pet? Most of those values are data members of the derived classes of Pet. And there is no such thing as virtual constructors. Are we calling up a virtual clone member function that imitates a virtual constructor?
Last edited on
The example illustrates how to do it without polymorphism, and as you've found out first hand, it is confusing, which is why I recommended polymorphism. Essentially you need a different constructor for each combination of attributes you want to support.
Without polymorphism, but using the idea of multiple constructors:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <iostream>
#include <initializer_list>

enum Pet {Dog, Bird, Cat};  // etc...
enum Colour {Black, Brown, Yellow};  // etc...

struct MyPetsData {
  Pet pet;
  int catAge, dogAge;  // etc...
  double catWeight, dogWeight;  // and many more
  Colour birdColour, dogColour, catColour;  // etc...
  bool birdCanFly, dogWillBite;  // etc...
  std::string birdName;  // etc...
  // ... 
  MyPetsData (Pet p, int a, double b, Colour c):  pet (p) {
  	switch (p) {
	  case Cat:  catAge = a;  catWeight = b;  catColour = c;  break;
  	  case Dog:  dogAge = a;  dogWeight = b;  dogColour = c;  dogWillBite = false;  break;
  	}
  }
  MyPetsData (Pet p, int a, double b, Colour c, bool d):  pet (p) {
  	switch (p) {
	  case Dog:  dogAge = a;  dogWeight = b;  dogColour = c;  dogWillBite = d;  break;
	}
  }
  MyPetsData (Pet p, Colour a, bool b, const std::string& c):  pet (p) {
    switch (p) {
	  case Bird:  birdColour = a;   birdCanFly = b;  birdName = c;  break;
	}
  }
  // etc...
};

struct MyPets {
  int catAge, dogAge;  // etc...
  double catWeight, dogWeight;  // and many more
  Colour birdColour, dogColour, catColour;  // etc...
  bool birdCanFly, dogWillBite;  // etc...
  std::string birdName;  // etc...
  // ... 
  MyPets (const std::initializer_list<MyPetsData>& petsList) {
  	for (const MyPetsData& x: petsList)
  	{
  	  switch (x.pet) {
		case Cat:  catAge = x.catAge;  catWeight = x.catWeight;  catColour = x.catColour;  break;
		case Dog:  dogAge = x.dogAge;  dogWeight = x.dogWeight;  dogColour = x.dogColour;  dogWillBite = x.dogWillBite;  break;
		case Bird:  birdColour = x.birdColour;  birdCanFly = x.birdCanFly;  birdName = x.birdName;  break;
  		// etc...
  	  }
  	}
  }
};

void listOutMyPets (const MyPets& myPets) {
  std::cout << "My cat: " << myPets.catAge << " years old, " << myPets.catWeight << " pounds, "
    << myPets.catColour << "." << std::endl;
  std::cout << "My dog: " << myPets.dogAge << " years old, " << myPets.dogWeight << " pounds, " 
    << myPets.dogColour << ", will bite = " << myPets.dogWillBite << "." << std::endl;
  std::cout << "My bird: " << myPets.birdName << ", " << myPets.birdColour << 
    ", can fly = " << myPets.birdCanFly << std::endl;
}

int main() {
  std::cout.setf (std::ios_base::boolalpha);
  listOutMyPets ({ 
  	{Cat, 8, 10.4, Black}, 
	{Dog, 6, 12.5, Brown, true}, 
	{Bird, Yellow, true, "Polly"} 
  });
  std::cin.get();
}


Output:
My cat: 8 years old, 10.4 pounds, 0.
My dog: 6 years old, 12.5 pounds, 1, will bite = true.
My bird: Polly, 2, can fly = true.

Did not have to create any new classes for the pets (and will not need them for anything else anyway). Still wondering if there is a shorter way to do this, without creating new classes for each pet. The duplicate data members smells afoul to me. I know that variadic templates could do the job using much shorter code, and probably with perfect forwarding too, but it is darn confusing.
Last edited on
Emulating C99 style 'designated initializers':

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <iostream>
#include <utility>
#include <initializer_list>
#include <boost/variant.hpp>
#include <vector>

enum type_t { Dog, Bird, Cat /* ... */ };
enum colour_t { brown, tan, fawn, pearl_gray /* ... */ };
enum designator_t { type, age, colour, weight, bite, fly };

struct pet
{

    type_t type_ = Dog ;
    int age_ = 1 ;
    colour_t colour_ = brown ;
    double weight_ = 1.0 ;

    union
    {
        bool will_bite_ = false ;
        bool can_fly_ ;
    };

    using variant = boost::variant<type_t,int,colour_t,double> ;

    pet( std::initializer_list< std::pair< designator_t, variant > > ilist )
    {
        for( const auto& pair : ilist )
        {
            try
            {
                switch( pair.first )
                {
                    case type: type_ = boost::get<type_t>(pair.second) ; break ;
                    case age: age_ = boost::get<int>(pair.second) ; break ;
                    case colour: colour_ = boost::get<colour_t>(pair.second) ; break ;
                    case weight: weight_ = boost::get<double>(pair.second) ;
                    case bite: will_bite_ = boost::get<bool>(pair.second) ;
                    case fly: can_fly_ = boost::get<bool>(pair.second) ;
                }
            }
            catch(...) {}
        }
    }
};

int main()
{
    std::vector<pet> menagerie
    {
        { { type, Dog }, { colour, tan }, { bite, true } },
        { { type, Cat }, { age, 3 } },
        { { type, Bird }, { colour, pearl_gray }, { weight, 0.25 }, { fly, true }  }
    };
}

Topic archived. No new replies allowed.