1

I really like C++17's auto template parameters as I don't have to jump through hoops in order to use non-type template arguments (such as functions with forwarded arguments).

But it got me thinking if it was possible to combine it with some other type (such as std::optional) in cases where there's no valid result from the forwarded function. E.g. something like:

#include <iostream>
#include <optional>

template <auto Func, typename E, typename ...Args>
auto safeCaller(Args&& ...args)
{
    // this could even be wrapped in a loop for retrying
    try
    {
        return Func(std::forward<Args>(args)...);
    }
    catch (E &e)
    {
        // ... perform some logging perhaps? or whatever else is relevant
        return std::nullopt;
    }
}

int foo(std::string bar)
{
    return bar.size();
}

int main()
{
    // specialise safeCaller to work on foo and to expect std::runtime_error
    auto result = safeCaller<foo, std::runtime_error>("baz");
    if (result)
    {
        std::cout << *result << std::endl;
    }
    return 0;
}

Right now this has several problems:

main.cpp: In instantiation of ‘auto safeCaller(Args&& ...) [with auto Func = foo; E = std::runtime_error; Args = {const char (&)[4]}]’:
main.cpp:25:60:   required from here
main.cpp:14:21: error: inconsistent deduction for auto return type: ‘int’ and then ‘std::nullopt_t’
   14 |         return std::nullopt;
      |                     ^~~~~~~
main.cpp:14:21: error: ‘struct std::nullopt_t’ used where a ‘int’ was expected
main.cpp: In function ‘int main()’:
main.cpp:28:23: error: invalid type argument of unary ‘*’ (have ‘int’)
   28 |         std::cout << *result << std::endl;

This is a toy example but I was hoping to have something that acts as a decorator to functions/calls that might lend themselves to some generalisable exception handling, clean up and/or logging.

I'm open to alternatives to std::optional as long as there's a way to communicate that the call could not complete and so no result could be returned.

Nobilis
  • 7,310
  • 1
  • 33
  • 67
  • This question doesn't really have anything to do with `auto` template parameters (although it does use them); rather it is entirely to do with `auto` return-type deduction and `optional`. – Human-Compiler Jul 08 '21 at 16:23

1 Answers1

3

You could deduce what type of std::optional you want from the function passed in.

Then you can return an empty optional if it throws.

#include <iostream>
#include <optional>

template <auto Func, typename E, typename ...Args>
auto safeCaller(Args&& ...args)
{
    using ret = std::optional<decltype(Func(std::forward<Args>(args)...))>;
    // this could even be wrapped in a loop for retrying
    try
    {
        return ret{Func(std::forward<Args>(args)...)};
    }
    catch (E &e)
    {
        // ... perform some logging perhaps? or whatever else is relevant
        return ret{};
    }
}

int foo(std::string bar)
{
    return bar.size();
}

int main()
{
    // specialise safeCaller to work on foo and to expect std::runtime_error
    auto result = safeCaller<foo, std::runtime_error>("baz");
    if (result)
    {
        std::cout << *result << std::endl;
    }
    return 0;
}
super
  • 12,335
  • 2
  • 19
  • 29
  • This is neat, I wouldn't have thought of manipulating the `optional` type like that. I think it's what I need. – Nobilis Jul 08 '21 at 17:15