Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In C++ 11, how do I specialize a function template that takes a function object based on return type?

I have a wrapper function in C++ 11, designed to be used with lambdas, like so:

template<typename Func>
int WrapExceptions(Func&& f)
{
  try
  {
    return f();
  }
  catch(std::exception)
  {
    return -1;
  }
}

And I can call it like so:

int rc = WrapExceptions([&]{
  DoSomething();
  return 0;
});
assert(rc == 0);

And life is OK. What I want to do, though, is overload or specialize the wrapper function such that when the inner function returns void, the outer function returns a default value of 0, e.g.:

int rc = WrapExceptions([&]{
  DoSomething();
});
assert(rc == 0);

Can I actually do this in C++ 11? I cannot for the life of me think of how.

like image 824
John Doty Avatar asked Sep 11 '14 21:09

John Doty


People also ask

When we specialize a function template it is called?

To do so, we can use a function template specialization (sometimes called a full or explicit function template specialization) to create a specialized version of the print() function for type double.

What is a function template creating a function without having to specify the exact type?

Explanation: Function template is used to create a function without having to specify the exact type. 2. Which is used to describe the function using placeholder types? Explanation: During runtime, We can choose the appropriate type for the function and it is called as template type parameters.

What is the specialty of a template function give example?

Template in C++is a feature. We write code once and use it for any data type including user defined data types. For example, sort() can be written and used to sort any data type items. A class stack can be created that can be used as a stack of any data type.

How do you define a function template?

Defining a Function Template In the above code, T is a template argument that accepts different data types ( int , float , etc.), and typename is a keyword. When an argument of a data type is passed to functionName() , the compiler generates a new version of functionName() for the given data type.


2 Answers

You may use SFINAE:

  • with std::result_of

    template<typename Func>
    typename std::enable_if<
        std::is_convertible<typename std::result_of<Func()>::type, int>::value,
        int
        >::type
    WrapExceptions(Func&& f)
    {
      try { return f(); }
      catch(std::exception) { return -1; }
    }
    
    template<typename Func>
    typename std::enable_if<
        std::is_same<void, typename std::result_of<Func()>::type>::value,
        int
        >::type
    WrapExceptions(Func&& f)
    {
      try { f(); return 0; /* default value */ }
      catch(std::exception) { return -1; }
    }
    
  • with decltype:

    template<typename Func>
    auto
    WrapExceptions(Func&& f)
    -> typename std::enable_if<
        std::is_convertible<decltype(f()), int>::value,
        int
        >::type
    {
      try { return f(); }
      catch(std::exception) { return -1; }
    }
    
    template<typename Func>
    auto
    WrapExceptions(Func&& f)
    -> typename std::enable_if<
        std::is_same<void, decltype(f())>::value,
        int
        >::type
    {
      try { f(); return 0; }
      catch(std::exception) { return -1; }
    }
    
like image 128
Jarod42 Avatar answered Oct 26 '22 07:10

Jarod42


Maybe a bit over-engineered, but you can use tag dispatch:

#include <stdexcept>
#include <type_traits>
#include <utility>

namespace detail
{
    struct returns_convertible_to_int {};
    struct returns_void {};

    template<typename Func>
    int WrapException_dispatch(Func&& f, returns_convertible_to_int)
    {
        return f();
    }

    template<typename Func>
    int WrapException_dispatch(Func&& f, returns_void)
    {
        f();
        return 0;
    }

    template<typename T, typename dummy = void>
    struct dispatch
    {
        static_assert(std::is_same<T, void>::value,
                      "Incompatible return type");
    };

    template<typename T>
    struct dispatch<T,
        typename std::enable_if< std::is_convertible<T, int>{} >::type>
    {
        using type = returns_convertible_to_int;
    };

    template<typename T>
    struct dispatch<T,
        typename std::enable_if< std::is_same<T, void>{} >::type>
    // alt: template<> struct dispatch<void,void>
    {
        using type = returns_void;
    };
}

template<typename Func>
int WrapException(Func&& f)
{
    try
    {
        return detail::WrapException_dispatch( std::forward<Func>(f),
            typename detail::dispatch<decltype(f())>::type{} );
    }
    catch(std::exception const&) { return -1; }
}

Usage example:

int foo() { return 42; }
void bar() {}

int main()
{
    WrapException(foo);
    WrapException(bar);
}

You can, of course, implement a shorter dispatch:

namespace detail
{
    template<typename Func>
    auto WrapException_dispatch(Func&& f, int)
    -> typename std::enable_if<
           std::is_convertible<decltype(f()), int>::value, int
       >::type
    {
        return f();
    }

    template<typename Func>
    int WrapException_dispatch(Func&& f, ...)
    {
        f();
        return 0;
    }
}

template<typename Func>
int WrapException(Func&& f)
{
    try
    {
        return detail::WrapException_dispatch( std::forward<Func>(f), 0 );
    }
    catch(std::exception const&) { return -1; }
}
like image 36
dyp Avatar answered Oct 26 '22 08:10

dyp