0

What's the best way to achieve:

for (const auto &arg : args) {
    try {
        auto var = create(arg);
    } catch (const std::invalid_argument &e) {
        continue;
    }
    // ...
    // use(var)
    // ...
}

One way is to move

    // ...
    // use(var)
    // ...

into the try block, but this introduces a whole load of nesting. Also, I don't want to catch errors from use

typeof(var) is a class with no default constructor.

Tobi Akinyemi
  • 804
  • 1
  • 8
  • 24

4 Answers4

1

One way is to move use(var) into the try block, but this introduces a whole load of nesting

You could move use(var) out to a separate function and call that in your try block:

void use(var_type var) {
    //...
}
for(const auto& arg : args) {
    try {            
        use(create(arg));
    } catch(const std::invalid_argument& e) {
        continue;
    }
}

Since you have no control over create or the use block and want to make sure that you don't catch anything thrown in the use block, you can catch and rethrow the create exception as a different type to be able to catch that only. Since exceptions should only happen in exceptional cases, this overhead should not be any problem.

Example:

struct create_error : std::invalid_argument {
    using std::invalid_argument::invalid_argument;
    explicit create_error(const std::invalid_argument& e) : std::invalid_argument(e) {}
};

auto create_wrapper(const arg_type& arg) {
    try {
        return create(arg);
    }
    catch(const std::invalid_argument& ex) {
        throw create_error(ex);
    }
}

void use(var_type var) {
    // may throw std::invalid_argument, but it won't be caught below
}
for(const auto& arg : args) {        
    try {
        use(create_wrapper(arg));
    }
    catch(const create_error& e) {
        continue;
    }        
}
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • If an `invalid_argument` is thrown from `use(var)` then it would be caught - that may not be the intention of the programmer who may only want to catch `invalid_argument` only for `create` but not `use`. – Dai Jul 26 '20 at 12:13
  • This was another idea I had, but this is not really useful - as it introduces indirection – Tobi Akinyemi Jul 26 '20 at 12:13
  • Also @Dai is correct. That's another reason (i forgot) that I wanted the code outside the try block – Tobi Akinyemi Jul 26 '20 at 12:14
  • Ok, both `create` and `use` may throw `invalid_argument`? Perhaps there's some underlaying design issue to take care of in that case. Create separate exceptions for the two functions. Indirection: I wouldn't worry about the reference being passed to `use` I doubt you'll be able to measure the effect. – Ted Lyngmo Jul 26 '20 at 12:15
  • @TedLyngmo there's no issue. I didn't claim that `use` throws an `invalid_argument` exception, but I'm only interested in catching the error thrown from `create` - thus, It's all that needs to be in the try block. – Tobi Akinyemi Jul 26 '20 at 12:24
  • Ok, but if it's only `create` that may throw `invalid_argument` then calling `use` as suggested should do exactly what you need. – Ted Lyngmo Jul 26 '20 at 12:24
  • @TedLyngmo Okay, lets say `use` does throw an invalid argument exception; that's not a problem. So, this isn't a valid solution. Above you assume there's some fundamental problem with the code base - which is confusing, it's a valid scenario – Tobi Akinyemi Jul 26 '20 at 12:25
  • throws a `std::invalid_argument` exception* – Tobi Akinyemi Jul 26 '20 at 12:28
  • Then, define different exceptions for the two functions as I suggested? – Ted Lyngmo Jul 26 '20 at 12:30
  • @TedLyngmo I'm not in control of the errors thrown; that's why I make no claims on what's thrown by `use`. Also, defining other errors is not a solution, it's a hack – Tobi Akinyemi Jul 26 '20 at 12:37
  • That's a pretty odd situation then, but you are in control of the `use` block so you can make that throw something else than `invalid_argument`? "_defining other errors is not a solution, it's a hack_" - What on earth gave you that idea?! If you look at many of the best libraries around, you'll find a lot of added exceptions. The standard exceptions provide a base but can't possibly fit every case. – Ted Lyngmo Jul 26 '20 at 12:40
  • @TedLyngmo I'm not in control of `use` - even if I were, there are other calls internally I wouldn't be in control of. – Tobi Akinyemi Jul 26 '20 at 13:57
  • 1
    Can you make a [mcve] and put that in your question? It's starting to feel like you're trapped in a bad situation. I added an alternative. – Ted Lyngmo Jul 26 '20 at 14:17
  • The code is moved into the try block, I explicit said this was something I didn't want - so that is the part of the answer that doesn't fulfill my needs. – Tobi Akinyemi Jul 28 '20 at 17:54
  • @TobyAkinyemi The reason you gave doesn't really apply when moving it to a separate function though - and it'll be hard to make a more effective solution. – Ted Lyngmo Jul 28 '20 at 18:19
1

Maybe std::optional is what you need. you can use it with Class with no default constructor, as follows

If you are not interested to know for what values the exceptions have been thrown, you can use something like the following

#include <iostream>
#include <optional>

struct A{
    int var;
    explicit A(int arg):var{arg}{}
    A() = delete ;
};
A create( int arg){
    if (arg == 5 || arg == 8)
        throw std::invalid_argument{""};
    return A{arg};
}

void use(const A & a)
{
    std::cout << "\n" << a.var;
}
int main()
{
    std::optional<A> a;
    for (size_t i{}; i < 10; ++i) {
        try {
            a = create(i);
        } catch (const std::invalid_argument &e) {
                continue;
        }
        if(a) use(a.value());

    }

}

Live

asmmo
  • 6,922
  • 1
  • 11
  • 25
  • 1
    If you're (ab)using a `vector` to represent "zero-or-one-of-`T`" then why use `optional` at all (or rather: why use `vector` when you have `optional` already)? With `vector` the space for `T` may be allocated on the heap, with `optional` it's guaranteed that the space will be allocated locally). – Dai Jul 26 '20 at 12:15
  • Could you strip out the vector usage and the second loop - that's outside the scope of this question - I understand you may have used it for prototyping your answer – Tobi Akinyemi Jul 26 '20 at 12:16
  • @TobiAkinyemi since you have a range of vars you need a container. right – asmmo Jul 26 '20 at 12:18
  • I store the object in a local variable, not an array. I'm not interested in old values - this is overcomplicating the scenario – Tobi Akinyemi Jul 26 '20 at 12:20
  • @asmmo The local variable is inside the loop; you also don't have a `use`; this isn't the same scenario. – Tobi Akinyemi Jul 26 '20 at 12:49
  • @asmmo Why not have it as your answer? Neither of the snippets in this post are solutions to the scenario. – Tobi Akinyemi Jul 26 '20 at 13:08
  • Also, `use` doesn't take an `optional` as parameter, but a `typeof(var)` – Tobi Akinyemi Jul 26 '20 at 13:09
  • @asmmo It's what you assume to be more general purpose. It also means you've consciously posted something that's not an answer. You've also overcomplicated the scenario – Tobi Akinyemi Jul 26 '20 at 13:17
1

Something like this will work:

template<typename T>
struct initializing {
    T self;
    template<typename F>
    initializing(F &&f) : self(std::forward<F>(f)()) { }
};

for(auto const &arg : args) {
    std::optional<initializing<std::decay_t<decltype(create(arg))>>> var_opt;
    try { var_opt.emplace([&]() -> decltype(auto) { return create(arg); }); }
    catch(std::invalid_argument const &e) { continue; }
    auto &var = var_opt->self;
}

Complete example

We're using std::optional essentially just to provide uninitialized storage for our value. The whole initializing thing is very likely unnecessary: it avoids performing any move or copy constructions whatsoever. If you instead use std::optional<std::decay_t<decltype(create(arg))>> var_opt and var_opt.emplace(create(arg)), then if create returns by value, that value will materialize as a temporary, and a reference to that temporary will be passed to emplace. The final object will then be move constructed from that temporary. Generally, that's good enough.

HTNW
  • 27,182
  • 1
  • 32
  • 60
  • I think this is the best answer so far. The only downside is the uninitialised memory within the optional - which is something I was trying to avoid – Tobi Akinyemi Jul 28 '20 at 17:55
-1

I think that it is mostly dependent on the variable type.

For example, if it's a pointer (smart or dumb) you can set it to nullptr before the try block and check if it was assigned outside of the try block.

If it's another type that can't get nullptr you can use a default value if possible. If this is not possible as well - well you should give a concrete example and maybe you could get a better answer.

Dror Moyal
  • 398
  • 3
  • 11