hkBattousai
hkBattousai

Reputation: 10941

Initializing fixed number of variables with an initializer list

I want to initialize an object with an initializer list. The problem is, an initializer list is able to contain unpredictable number of elements, but I need to initialize only for variables. The user may send any number of list elements, and I need only four of them.

I wrote the following code, but it looks like very long and inefficient to me. Is there any better way of doing this?

Pixel::Pixel(std::initializer_list<uint8_t> Channels)
{
    switch (Channels.size())
    {
        case 0:
            R = 0;
            G = 0;
            B = 0;
            A = 0;
            break;
        case 1:
            R = *(Channels.begin() + 0);
            G = 0;
            B = 0;
            A = 0;
            break;
        case 2:
            R = *(Channels.begin() + 0);
            G = *(Channels.begin() + 1);
            B = 0;
            A = 0;
            break;
        case 3:
            R = *(Channels.begin() + 0);
            G = *(Channels.begin() + 1);
            B = *(Channels.begin() + 2);
            A = 0;
            break;
        default:
            R = *(Channels.begin() + 0);
            G = *(Channels.begin() + 1);
            B = *(Channels.begin() + 2);
            A = *(Channels.begin() + 3);
    }
}

(Note: I know this can be done with passing the R, G, B, A values with four separate arguments. But my main purpose is to learn how to do this with the initializer list feature.)

Upvotes: 1

Views: 409

Answers (2)

Richard Hodges
Richard Hodges

Reputation: 69942

the best I could come up with when using std::initialiser_list

struct Pixel
{
    Pixel(std::initializer_list<uint8_t> rgba)
    : _rgba { rgba }
    {
        switch(_rgba.size()) {

            case 0: _rgba.push_back(0);
            case 1: _rgba.push_back(0);
            case 2: _rgba.push_back(0);
            case 3: _rgba.push_back(0);
            case 4: break;
            default:
                throw std::invalid_argument { "" };
        }

    }

    std::vector<uint8_t> _rgba;
};

... but ...

Probably the correct way to solve this problem is this:

struct Pixel
{
    Pixel(uint8_t r = 0, uint8_t g = 0, uint8_t b = 0, uint8_t a = 0)
    : R(r)
    , G(g)
    , B(b)
    , A(a)
    {}

    uint8_t R,G,B,A;
};

because

  1. It will fail to compile if you provide invalid arguments
  2. It's optimally efficient
  3. It's the least surprising solution for anyone maintaining your code
  4. It automatically supports the initializer_list syntax at the call site

examples:

int main()
{
    Pixel p1 { 10, 20, 5, 255 };
    Pixel p2 { 10, 20, 5 };
    Pixel p3 { 10, 20 };
    Pixel p4 { 10 };
    Pixel p5 { };

    Pixel pv1 ( 10, 20, 5, 255 );
    Pixel pv2 ( 10, 20, 5 );
    Pixel pv3 ( 10, 20 );
    Pixel pv4 ( 10 );
    Pixel pv5;

    return 0;
}

Upvotes: 4

Jarod42
Jarod42

Reputation: 218323

You may rewrite you switch as follow:

Pixel::Pixel(std::initializer_list<uint8_t> Channels) : R(0), G(0), B(0), A(0)
{
    switch (Channels.size())
    {
        default: // Too many args
        case 4: A = *(Channels.begin() + 3); // No break: Follow next line
        case 3: B = *(Channels.begin() + 2); // No break: Follow next line
        case 2: G = *(Channels.begin() + 1); // No break: Follow next line
        case 1: R = *(Channels.begin() + 0);
        case 0: break;
    }
}

Upvotes: 4

Related Questions