1
return a or b or c or d;

That statement returns either true or false in C++, and I know the reason for this.

But I need a workaround so that I can return the first non-zero value via that return statement (or something similar) like it happens in Python.

I am not looking for conditional statements as it looks untidy sometimes.

Basically, can the following code be shortened via a macro or something else?

int fun(int a, int b, int c, int d)
{
    return a ? a : b ? b : c ? c : d;
}
Ardent Coder
  • 3,777
  • 9
  • 27
  • 53

2 Answers2

4

The task can be accomplished by using the necessary function in the return statement.

For example, instead of a macro, I used a templated function which accepts the parameters in std::initializer_list:

template <typename T>
T OR(const std::initializer_list<T>& args)
{
    for (const T& arg : args)
        if (arg)
            return arg;
    return T{};
}

It can be used as follows for the given problem:

return OR({a, b, c, d});

The problem in that link can also be solved in this way:

return OR({(m + s - 1) % n, n});

Note that it depends on the implicit boolean conversion of a given type T. So, for example, an empty std::string is not false. Also, a user-defined type should have operator bool() const in order to comply with this workaround.

P.S. While I was trying to ask my mistake in a variadic template solution in the question, I discovered this solution myself :P

Note:

See this answer to know the limitations of this way when working with more complex designs.

Ardent Coder
  • 3,777
  • 9
  • 27
  • 53
  • if I understand OP correctly they insist on a one-liner. Your answer just shifts the code from `fun` to a different function. Nevertheless, I agree that this is the way to go – 463035818_is_not_an_ai May 05 '20 at 09:17
  • 2
    oh, you are the OP ;) – 463035818_is_not_an_ai May 05 '20 at 09:18
  • @idclev463035818 But I'm not ticking my solution, maybe you could show me a better way like I was trying to do with variadic templates. Perhaps something which would also work with heterogeneous parameters. – Ardent Coder May 05 '20 at 09:26
  • 2
    `initializer_list` seems better IMO, variadic method would require to return one unique type (`std::common_type`?) anyway. – Jarod42 May 05 '20 at 09:28
  • tbh I would actually prefer `if (a) return a; if (b) return b; ...etc..` in `fun` instead of a helper. Trying to mimic python in c++ isnt the best motivation to write clean code. At least you should give the function a better name, perhaps `first_non_zero` – 463035818_is_not_an_ai May 05 '20 at 09:32
  • 2
    also note that once you write a function you loose the ability to short-circuit. – 463035818_is_not_an_ai May 05 '20 at 09:34
  • @idclev463035818 Isn't the function mimicking short-circuit when it returns at the first non-zero value? Or are you referring to a short-circuit which I don't know yet, lol I'm not a very advanced C++ programmer. – Ardent Coder May 05 '20 at 09:45
  • @Jarod42 I would still like to see that kind of a solution to make the code more generic – Ardent Coder May 05 '20 at 09:49
  • 1
    suppose you call `OR` not with `int`s but with `OR({ some_function(),some_other_function()})` then both functions must be called so `OR` can be called. If you write `int temp = some_function(); if (temp) return temp; temp = some_other_function(); if (temp) return temp;` then in case `some_function` returns non-zero the other will not be called, that is short-cuirciting – 463035818_is_not_an_ai May 05 '20 at 09:49
  • @idclev463035818 Wow, I didn't think like that! I hope someone will come up with a solution that doesn't have such drawbacks. – Ardent Coder May 05 '20 at 09:51
  • 1
    i makes no difference for your `fun` example, but it is an argument against using such function in general and maybe part of the reason you didn't find something like this in C++ (but in python) – 463035818_is_not_an_ai May 05 '20 at 09:51
  • @ArdentCoder there is no (simple) way to avoid that. You need the parameters before you can call a function – 463035818_is_not_an_ai May 05 '20 at 09:52
  • 1
    @idclev463035818: or parameter should be generator/callable. but more verbose in simple case. – Jarod42 May 05 '20 at 09:53
  • @Jarod42 yeah I thought about that, but this again introduces overhead and indeed obfuscated the call-site. I am still in favour for a simple `if-else`. I have been trhough a phase of writing helpers for each an everything, but there is no need to invent something when a language construct (`if-else`) already does the job – 463035818_is_not_an_ai May 05 '20 at 09:55
4

I would write the function like this:

int fun(int a, int b, int c, int d) {
    if (a) return a;
    else if (b) return b;
    else if (c) return c;
    return d;
}

It is clean and short. I could stop here, but lets explore what can be done...

There exists an algorithm that already almost does what you want. A slight modification of the solution in this answer:

#include <algorithm>
#include <initializer_list>

template <typename T>
T first_non_zero_or_zero(const std::initializer_list<T>& args)
{
    auto it = std::find_if_not(args.begin(),args.end(),[](auto v){ return v==0;});    
    return (it != args.end()) ? *it : 0;
}

The drawback of using a function for boolean expressions is no short-cuirciting. If you call the function via:

auto x = first_non_zero_or_zero( { foo(), expensive_function() });

Then expensive_function must be called, no matter what foo returns. The way to restore the ability to short-circuit is to pass callables instead, that would be

template <typename F>
auto first_non_zero_or_zero(F f){ return f();}

template <typename First,typename...F>
auto first_non_zero_or_zero(First f,F... others){    
    if (auto temp = f()) return temp;
    return first_non_zero_or_zero(others...);
}

int foo(){ return 0;}
int expensive_function(){ return 42;}

int main()
{
    std::cout << first_non_zero_or_zero(foo,expensive_function);
    return 0;
}

However, this will make calls unnecessarily verbose when called with simple ints, as you need to wrap them in a callable:

int fun(int a,int b,int c) {
    first_non_zero( [](){ return a;},
                    [](){ return b;},
                    [](){ return c;})
}

Conclusion: Don't make things more complicated than necessary. Functions should do one thing. The one thing your fun does is to return the first non-zero of 4 integers and a if-else is the most simple way to get that done.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • My main aim does not revolve around `fun`, that was just a minimal reproducible example. What I wanted was a generic one-liner way to mimic that Python behaviour :p But I like the deep concepts covered in this answer. – Ardent Coder May 05 '20 at 10:41
  • 1
    @ArdentCoder as I said before, C++ is not Python. Lot can be learned by comparing two lanugages and try to see if what can be done in one can also be done in the other, but usually what is idiomatic in one can be crap in the other. Also one-liners arent always the most readable. Anyhow, I had my fun with exploring the issue, but imho the conclusion is as important as the rest of the anwer – 463035818_is_not_an_ai May 05 '20 at 10:44
  • Haha me too, it was fascinating how such a simple problem can make us explore many things ;) – Ardent Coder May 05 '20 at 10:45
  • Are you online? I have a doubt – Ardent Coder May 09 '20 at 08:43