26

I was wondering if there was a way to put this on one line?

if (auto r = getGlobalObjectByName(word)) r->doSomething; // This works fine

if (!auto r = getGlobalObjectByName(word)) r->doSomething; // Says "expected an expression"

if (auto r = getGlobalObjectByName(word) == false) r->doSomething; // Also doesn't work.

I also tried surrounding it with extra brackets, but that doesn't seem to work. I find this really convenient doing it on one line.

Boann
  • 48,794
  • 16
  • 117
  • 146
Zebrafish
  • 11,682
  • 3
  • 43
  • 119
  • 24
    As a side note, I would discourage trying to one line that. In fact, I don't even encourage you to use the return value of an assignment in your conditional, [various compilers actually treat that as a compiler warning](https://msdn.microsoft.com/en-us/library/7hw7c1he.aspx) as it leads easily to bugs. Let alone adding a third step in that task. Make sure your code is readable first and foremost, terse isn't always better. – Cory Kramer Sep 01 '17 at 11:16
  • 4
    I fail to see the point. If `r` tests as `false`, then evaluating `r->doSomething` will give undefined behaviour, unless a very specific set of conditions are met (such as `r` being of some class type, that class type providing an `operator!()` or being comparable with `bool`, AND providing an `operator->()` that does not somehow yield a null pointer if the object tests as being `false`) – Peter Sep 01 '17 at 11:29
  • 6
    @CoryKramer That is not assignment it is initialization and no compilers will give a warning for this. You must be confuding it with the warning compilers give for assignment when the variable is declared before the `if` construct. In that case it serves to protect your from using assignment rather than equivalence. But this is different and can't be confused. – Galik Sep 01 '17 at 12:36
  • 3
    Having upvoted the comment that compilers won't warn, I still think this sort of excessive terseness is usually a bad idea. – Martin Bonner supports Monica Sep 01 '17 at 13:35
  • 1
    @MartinBonner I agree somewhat halfheartedly. IMO the only badness involved is the relative inexperience C++ programmers have with this syntax. Go went out of its way to make sure this is _the_ idiomatic way of writing, and I don't think it is in any way excessively terse – Passer By Sep 01 '17 at 13:46
  • @PasserBy : The problem with `if (auto a = ...)` is that you are doing multiple things in the same line. Firstly you are initializing `a`, secondly, you are testing the result of that initialization. In general, I prefer to only do *one* think on one line. – Martin Bonner supports Monica Sep 01 '17 at 13:49
  • @MartinBonner In my very little experience, the condition directly depends on the init-statement, and is often trivial, as in this case. Although lumping things together isn't in general a good idea, in this context, I find it perfectly fine – Passer By Sep 01 '17 at 13:51
  • 3
    `auto r = getGlobalObjectByName(word) == false` mean `auto r = (getGlobalObjectByName(word) == false)` – phuclv Sep 01 '17 at 16:20
  • Not a direct answer to your question, but you may wish to consider a null object so that the `getGlobalObjectByName()` call could always succeed, but `doSomething()` has a noop implementation in the null object. Then you don't have to test `r`, you could just `getGlobalObjectByName(word)->doSomething();` – Andre Kostur Sep 01 '17 at 19:07
  • I'm surprised the first one works. – Hot Licks Sep 02 '17 at 01:52
  • @HotLicks The first construct also works for `for` statements, so why not for `if`s? – Mr Lister Sep 02 '17 at 07:34

5 Answers5

52

Since C++17 you can use an initializer if-statement:

if (auto r = getGlobalObjectByName(word); !r) r->doSomething;

The semantics are:

if (init-statement; condition) statement

The only difference from the "traditional" if-statement is the init-statement, which initializes a variable in the block scope, similar to for-loops.

Massimiliano Kraus
  • 3,638
  • 5
  • 27
  • 47
Passer By
  • 19,325
  • 6
  • 49
  • 96
13

If you have C++17, use the if (init statement; condition) form. If not, you have three choices:

  • Stop trying to keep this all on one line. For example:

    auto r = getGlobalObjectByName(word);
    if (!r) r->doSomething();
    
  • Use an else:

    if (auto r = getGlobalObjectByName(word)) {} else r->doSomething();
    

(Note that this requires that r is a smart pointer with very strange semantics for the operator bool() function. OTOH, I assume this is actual a short piece of example code, rather than your actual code).

I think I would only use the else form if it was really important to keep everything on one line (to preserve a tabular formatting of the code for example).

  • `r` may be something other than a smart pointer, e.g. a `Status` class, which converts to `false` to indicate failure and can e.g. provide an error message in this case. – Ruslan Sep 01 '17 at 15:26
  • @Ruslan Yes, it could, but overloading `operator ->` to provide error messages seems like operator overloading abuse to me. `r.Error().doSomething()` would be cleaner. – Martin Bonner supports Monica Sep 01 '17 at 15:53
  • Well yes indeed, didn't think of the `operator->`. – Ruslan Sep 01 '17 at 15:54
4

What you are trying to do is fine. By defining the variable inside the if you restrict its scope. That helps to reduce the number of stray variables after they have served their purpose.

Using this technique, if you want to follow the negative path, you need to use else like this:

if(auto r = getGlobalObjectByName(word))
{
    r->doSomething();
}
else
{
    // r == nullptr
    // so do something else
}
Galik
  • 47,303
  • 4
  • 80
  • 117
4

There's also a way to do this with lambdas and C++14 but it does seem rather silly.

[](auto r){ if(!r)r->doSomething(); }(getGlobalObjectByName(word));

In C++11 you could also do this horrible mess (same idea, just no auto)

[](decltype(getGlobalObjectByName(word)) r){ if(!r)r->doSomething(); }(getGlobalObjectByName(word));

It is certainly no better than this clearer C++11 version mentioned by Martin Bonner:

{
    auto r = getGlobalObjectByName(word);
    if(!r)r->doSomething();
}

Here your code is clear that you want r to only be around for the duration of the if statement.

Erroneous
  • 535
  • 4
  • 12
  • I don't understand what's happening in the first line you wrote. You declare a lambda and then instantly call a function after it on the one line, as if the return value of the function is passed as the argument to the lambda. Is this a feature of C++14? What's it called? – Zebrafish Dec 29 '17 at 01:07
  • @Zebrafish the C++14 feature is auto parameters for lambdas. The code is simply constructing a lambda and immediately calling its operator(). If I used a type instead of auto I could do it in C++11. – Erroneous Dec 29 '17 at 15:39
2

Before C++17 you can define a wrapper class, like here:

#include <utility>
template<typename T>
class NotT
{
    T t;
public:
    template<typename U>
    NotT(U&& u) : t(std::move(u)) {}
    explicit operator bool() const { return !t; }
    T      & value()       { return t; }
    T const& value() const { return t; }
};
template<typename T> NotT<T> Not(T&& t)
{
    return NotT<T>(std::move(t));
}

#include <memory>
#include <iostream>

int main()
{
    if(auto p=Not(std::make_shared<int>(2134)))
        std::cout << "!p: p=" << p.value().get() << '\n';
    else
        std::cout << "!!p: p=" << p.value().get() << ", *p=" << *p.value() << '\n';

}

Live example

Ruslan
  • 18,162
  • 8
  • 67
  • 136