0

I have this section in my code where I am using an if else, and the ternary operator on the same bool condition. Is there a more elegant way to do this?

bool UseGroups //input parameter to a function.

    std::vector<std::vector<int>>& relevantGamesGroup = (useGroups) ? (objFlight.gamesGroup[dayIndex]) : (objFlight.gamesSubGroups[dayIndex]);

    if (useGroups) {
        numberOfGroups = objFlight.numberOfGroups[dayIndex];
    }
    else {
        numberOfGroups = 2 * (objFlight.numberOfGroups[dayIndex]);
    }
3
  • There's nothing wrong with the way you have it written. You could have another ternary after the reference assignment, i.e., numberOfGroups = (useGroups ? 2 : 1) * objFlight.numberOfGroups[dayIndex]; but this is arguably less clear, and may be slightly less efficient, depending on how the compiler optimizes. Commented Dec 28, 2021 at 16:36
  • Is there a typo in 2 * line? Should it use SubGroups there? Commented Dec 28, 2021 at 16:38
  • 1
    @CharlesSavoie objFlight.numberOfGroups[dayIndex] * (useGroups + 1) if you really want to be a smartass :) Commented Dec 28, 2021 at 16:39

5 Answers 5

5

I would probably write it like this, because I find it quite clear to read:

auto& relevantGamesGroup = useGroups
    ? objFlight.gamesGroup[dayIndex]
    : objFlight.gamesSubGroups[dayIndex];
auto numberOfGroups = useGroups
    ? objFlight.numberOfGroups[dayIndex]
    : objFlight.numberOfGroups[dayIndex] * 2;
Sign up to request clarification or add additional context in comments.

Comments

4

If you need to use the variables relevantGamesGroup and numberOfGroups after only having checked the condition once, you could create and call a temporary lambda that you make return the necessary pair:

auto&& [relevantGamesGroup, numberOfGroups] =
    [&]() -> std::pair<std::vector<std::vector<int>>&, int>
{
    if (useGroups) return {objFlight.gamesGroup[dayIndex],
                           objFlight.numberOfGroups[dayIndex]};
    return {objFlight.gamesSubGroups[dayIndex],
            2 * objFlight.numberOfGroups[dayIndex]};
}();

// use relevantGamesGroup and numberOfGroups here

An alternative using the ternary/conditional operator instead of using a lambda:

auto&& [relevantGamesGroup, numberOfGroups] =
    useGroups ? std::pair<std::vector<std::vector<int>>&, int>
                    {objFlight.gamesGroup[dayIndex],
                     objFlight.numberOfGroups[dayIndex]}
              : std::pair<std::vector<std::vector<int>>&, int>
                    {objFlight.gamesSubGroups[dayIndex],
                     2 * objFlight.numberOfGroups[dayIndex]};

// use relevantGamesGroup and numberOfGroups here

If you use this kind of construct a lot, creating a helper function could simplify it:

#include <tuple>

template<class... Ts>
std::tuple<Ts...> ternary(bool cond, std::tuple<Ts...>&& True,
                                     std::tuple<Ts...>&& False) {
    return cond ? True : False;
}

You'd then supply the wanted types as template parameters and use structured bindings to extract the selected values / references just like above:

int main() {
    int a1 = 1, b1 = 2, c1 = 3;
    int a2 = 40, b2 = 50, c2 = 60;

    auto&&[a,b,c] = ternary<int&,int&,int&>(true, {a1,b1,c1}, {a2,b2,c2});

    std::cout << a << b << c << '\n';
    ++a; ++b; ++c; // these are references to a1, b1 and c1
    std::cout << a1 << b1 << c1 << '\n';
}

Output:

123
234

With the types in your question, it could look like this:

void func(bool useGroups) {
    auto&& [relevantGamesGroup, numberOfGroups] =
      ternary<std::vector<std::vector<int>>&, int>(useGroups,
        {objFlight.gamesGroup[dayIndex],     objFlight.numberOfGroups[dayIndex]},
        {objFlight.gamesSubGroups[dayIndex], 2 * objFlight.numberOfGroups[dayIndex]});

    // use relevantGamesGroup and numberOfGroups here
}

2 Comments

Sure it works that way? I'm having trouble replicate: godbolt.org/z/xGcjEjo3q Can you have a look?
@Jellyboy Yes, you just forgot to call the lambda: godbolt.org/z/qdTf8Yfsd
2

Not sure whether it is more "elegant", but if you insist of writing only one if/else, then either use a pointer instead of reference for relevantGamesGroup which can be default-initialized and assigned later, or a lambda can help:

auto& relevantGamesGroup = [&]()->decltype(auto){
    if (useGroups) {
        numberOfGroups = objFlight.numberOfGroups[dayIndex];
        return objFlight.gamesGroup[dayIndex];
    } else {
        numberOfGroups = 2 * (objFlight.numberOfGroups[dayIndex]);
        return objFlight.gamesSubGroups[dayIndex];
    }
}();

(Note that ->decltype(auto) is important here, since the lambda will otherwise return by-value, not by-reference.)

And for completeness the clearly worse way of doing it with just one ternary operator:

auto& relevantGamesGroup = useGroups
    ? ((void)(numberOfGroups = objFlight.numberOfGroups[dayIndex]),
           objFlight.gamesGroup[dayIndex])
    : ((void)(numberOfGroups = 2 * (objFlight.numberOfGroups[dayIndex])), 
           objFlight.gamesSubGroups[dayIndex]);

((void) cast optional if you are not using some very weird type for numberOfGroups)

Comments

1

This code looks fine to me. Here's how I would rewrite it, but it's mostly a matter of style.

bool useGroups;

// Use of auto
auto& relevantGamesGroup = useGroups ? objFlight.gamesGroup[dayIndex] : objFlight.gamesSubGroups[dayIndex];

numberOfGroups = objFlight.numberOfGroups[dayIndex];
if (useGroups) {
    numberOfGroups *= 2;
}

Comments

1

This is clean and can be fixed by Joe the Intern if needed be.

using GroupGames = std::vector<std::vector<int>>;
GroupGames* relevantGamesGroup; 
if (useGroups) { 
    relevantGamesGroup = &objFlight.gamesGroup[dayIndex];
}
else {
    relevantGamesGroup = &objFlight.gamesSubGroups[dayIndex];
}

if (useGroups) {
    numberOfGroups = objFlight->numberOfGroups[dayIndex];
}
else {
    numberOfGroups = 2 * (objFlight->numberOfGroups[dayIndex]);
}

Or with the suggestion of @Ted Lyngmo below, it's even cleaner.

using GroupGames = std::vector<std::vector<int>>;
GroupGames* relevantGamesGroup = &objFlight.gamesSubGroups[dayIndex];
int numberOfGroups = 2 * (objFlight->numberOfGroups[dayIndex]);
if (useGroups) { 
    relevantGamesGroup = &objFlight.gamesGroup[dayIndex];
    numberOfGroups = objFlight->numberOfGroups[dayIndex];
}

7 Comments

@TedLyngmo good advice, thank you!
You're welcome! You could add auto& relevantGamesGroup = *relevantGamesGroupPtr; to get a nice reference after the if too.
@TedLyngmo I have a personal preference not to use "auto" unless strictly necessary. I work with mission critical systems and I have been bitten more than once by folks using auto and creating the wrong variable type with bad consequences. I like to go the extra mile and specify crystal clear what every type should be.
@Jellyboy Oups :-) Well, you could be explicit and make it GroupGames& relevantGamesGroup = *relevantGamesGroupPtr; instead.
@Jellyboy sure, here's one with relevance. In your first block of code I can see no reason to have two separate if clauses. It adds nothing to readability.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.