7

I have a template class that must perform some operation before calling a function whose parameters and return type are generic.

This is the method:

template <typename ReturnType, typename ...Args>
ReturnType function (Args ...args) {
  // prepare for call
  // ...
  ReturnType rv = makeCall(args...);  // [1]
  // dismiss the call
  // ...
  return rv;
}

Of course it's compiling correctly when ReturnType is not void. When I use it in this context:

function<void>(firstArg, secondArg);

The compiler responds with

error: return-statement with a value, in function returning 'void' [-fpermissive]

pointing to the line marked with [1].

Is there any solution other than passing -fpermissive to the compiler? I would prefer to have a unique method, because I possible solution I found is to instantiate different versions using enable_if and is_same.

Thank you in advance.

-- Update --

This is a complete example. I should have said that our functions are indeed class methods.

#include <type_traits>
#include <iostream>

class Caller {
public:
    Caller() {}

    template <typename ReturnType, typename ...Arguments>
    ReturnType call(Arguments ... args) {
        prepare();

        ReturnType rv = callImpl<ReturnType>(args...);

        done();

        return rv;
    }

private:
    void prepare() {
        std::cout << "Prepare\n";
    }

    void done() {
        std::cout << "Done\n";
    }

    template <typename ReturnType, typename ...Arguments>
    typename std::enable_if<std::is_same<ReturnType, void>::value, ReturnType>::type callImpl ( Arguments ... args) {
        std::cout << "Calling with void\n";
        return;
    }

    template <typename ReturnType, typename ...Arguments>
    typename std::enable_if<std::is_same<ReturnType, bool>::value, ReturnType>::type callImpl (Arguments ... args) {
        std::cout << "Calling with bool\n";
        return true;
    }

    template <typename ReturnType, typename ...Arguments>
    typename std::enable_if<std::is_same<ReturnType, int>::value, ReturnType>::type callImpl (Arguments ... args) {
        std::cout << "Calling with int\n";
        return 42;
    }
};


int main(int argc, char *argv[]) {

    Caller c;
    auto rbool = c.call<bool> (1,20);
    std::cout << "Return: " << rbool << "\n";
    auto rint = c.call<int> (1,20);
    std::cout << "Return: " << rint << "\n";

    // the next line fails compilation. compile with --std=c++11
    c.call<void>("abababa");

    return 0;
}

-- Update --

Not a big issue: Use std::bind(&Caller::callImpl<ReturnType>, this, args).

HappyCactus
  • 1,935
  • 16
  • 24
  • Did you try `function` ? – wdc Mar 10 '17 at 18:36
  • In C++17 you could probably use `constexpr if (std::is_same::value) { return; } else { return rv; }` (ok, that's not the whole solution since you should also not try to instantiate `void rv`, but you catch the drift). – einpoklum Mar 10 '17 at 18:38
  • Thank you @Someprogrammerdude, that's what I wanted to avoid, but at the moment it is the best solution – HappyCactus Mar 10 '17 at 19:03
  • Thank you @einpoklum, I knew of this solution but it's not feasible, since c++17 isn't supported in my context. Thank you anyway. – HappyCactus Mar 10 '17 at 19:04
  • Partial specialization is wrong, I don't really know what I was thinking, but it's not possible to partially specialize functions. Overloading, and using e.g. `enable_if` or some other type-trait might be a possible solution though. – Some programmer dude Mar 10 '17 at 19:09

4 Answers4

5

Here's my attempt at a general C++11-compliant solution that you can easily reuse.

Let's start by creating a simple type trait that converts void to an empty struct. This doesn't introduce any code repetition.

struct nothing { };

template <typename T>
struct void_to_nothing 
{
    using type = T;
};

template <>
struct void_to_nothing<void>
{
    using type = nothing;
};

template <typename T>
using void_to_nothing_t = typename void_to_nothing<T>::type; 

We also need a way to call an arbitrary function converting an eventual void return type to nothing:

template <typename TReturn>
struct helper
{
    template <typename TF, typename... Ts>
    TReturn operator()(TF&& f, Ts&&... xs) const
    {
        return std::forward<TF>(f)(std::forward<Ts>(xs)...);
    }
};

template <>
struct helper<void>
{
    template <typename TF, typename... Ts>
    nothing operator()(TF&& f, Ts&&... xs) const
    {
        std::forward<TF>(f)(std::forward<Ts>(xs)...);
        return nothing{};
    }
};

template <typename TF, typename... Ts>
auto with_void_to_nothing(TF&& f, Ts&&... xs)
    -> void_to_nothing_t<
           decltype(std::forward<TF>(f)(std::forward<Ts>(xs)...))>
{
    using return_type = 
        decltype(std::forward<TF>(f)(std::forward<Ts>(xs)...));

    return helper<return_type>{}(std::forward<TF>(f), std::forward<Ts>(xs)...);
}

Usage:

template <typename ReturnType, typename ...Args>
void_to_nothing_t<ReturnType> function (Args ...args) {
  // prepare for call
  // ...
  auto rv = with_void_to_nothing(makeCall, args...);  // [1]
  // dismiss the call
  // ...
  return rv;
}

live wandbox example


There's a proposal by Matt Calabrese called "Regular Void" that would solve this issue. You can find it here: "P0146R1".

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • 3
    A remaiing problem is that `function` no longer returns `void`, but rather `nothing`. This exposes an internal implementation detail. – Yakk - Adam Nevraumont Mar 10 '17 at 19:34
  • 1
    Thanks, this solution worked fine though as stated in the comment, the function returns `nothing` instead of `void`, this is perfectly acceptable for my purpose. – HappyCactus Mar 13 '17 at 12:09
5

Depending on what you wish to accomplish in the lines

// dismiss the call

you might be able to use:

template <typename ReturnType, typename ...Args>
ReturnType function (Args ...args) {
  // prepare for call
  // ...

  CallDismisser c;
  return  makeCall(args...);  // [1]
}

That would work as long as the destructor of CallDismisser can do everything you need to do.

R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • This is a possible solution, I haven't tested it but it seems to be correct. The only thing is that the flow isn't readable unless you know the side effect of the CallDismisser destructor. But thanks, it is a feasible solution. – HappyCactus Mar 10 '17 at 21:20
4
struct nothing {};


template<class Sig>
using returns_void = std::is_same< std::result_of_t<Sig>, void >;

template<class Sig>
using enable_void_wrap = std::enable_if_t< returns_void<Sig>{}, nothing >;
template<class Sig>
using disable_void_wrap = std::enable_if_t< !returns_void<Sig>{}, std::result_of_t<Sig> >;

template<class F>
auto wrapped_invoker( F&& f ) {
  return overload(
    [&](auto&&...args)->enable_void_wrap<F(decltype(args)...)> {
      std::forward<F>(f)(decltype(args)(args)...);
      return {};
    },
    [&](auto&&...args)->disable_void_wrap<F(decltype(args)...)> {
      return std::forward<F>(f)(decltype(args)(args)...);
    }
  );
}

so wrapped_invoker takes a function object, and makes it return nothing instead of void.

Next, holder:

template<class T>
struct holder {
  T t;
  T&& get()&& { return std::forward<T>(t); }
};
template<>
struct holder<void> {
  template<class T>
  holder(T&&) {} // discard
  void get()&& {}
};

holder lets you hold the return value and convert back to void if needed. You must create holder<T> using {} to get reference lifetime extension to work properly. Adding a ctor to holder<T> will break it.

holder<void> silently discards anything passed to it.

template <typename ReturnType, typename ...Args>
ReturnType function (Args ...args) {
  // prepare for call
  // ...
  holder<ReturnType> rv{ wrapped_invoker(makeCall)(args...) };
  // dismiss the call
  // ...
  return std::move(rv).get();
}

Now, holder<ReturnType> holds either nothing or the return value of makeCall(args...).

If it holds nothing, rv.get() returns void, and it is legal to return void to a function where ReturnValue is void.

Basically we are doing two tricks. First, we are preventing makeCall from returning void, and second if we are returning void we are discarding the return value of makeCall conditionally.

overload isn't written here, but it is a function that takes 1 or more function objects (such as lambdas) and returns their overload set. There is a proposal for std::overload, and a myriad of examples on stackoverflow itself.

Here is some:

Community
  • 1
  • 1
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • I like the `holder` idea. I assume that you're forcing the user to `std::move` the holder just to make it clear that it shouldn't be reused, correct? – Vittorio Romeo Mar 10 '17 at 19:43
  • @VittorioRomeo Yes, as I "forward" the result out, reusing it is not generally safe. Hmm, I need to tweak it now that I think about it, as a `ReturnType=int` can hold a `int&`, but my `holder` holds a `int&&` which refuses to hold an `int`. Fixed. – Yakk - Adam Nevraumont Mar 10 '17 at 20:41
  • does this require c++14 , right? My clang complains for the auto in lambda parameters. (I should have said also that `makeCall()` is a member function of the same class. I'll update the question with a complete source. Thanks) – HappyCactus Mar 10 '17 at 21:29
  • @HappyCactus Yes, `auto` return types are C++14. You can add a `->decltype( return_statement_goes_here )` for C++11 compatibility, or `#define RETURNS(...) decltype(__VA_ARGS__) { return __VA_ARGS__; }` then `auto wrapped_invoker( F&& f )->RETURNS( return_statement_goes_here )` to avoid repeating yourself. Also, you'd have to replace the `auto&&...` lambdas with function objects (note that many C++11 compilers supported `auto&&...` lambdas before they supported the rest of C++14, as it was really easy to write). – Yakk - Adam Nevraumont Mar 10 '17 at 22:15
  • Thanks, this was my preferred solution, except that I couldn't switch to c++14. Thank you anyway. – HappyCactus Mar 13 '17 at 12:11
1

The problem seems to be with //Dismiss the call.

This code shouldn't exist. That's what we have RAII for. The following code does work, even with ReturnType = void.

template <typename ReturnType, typename ...Arguments>
ReturnType call(Arguments ... args) {
    Context cx;
    return callImpl<ReturnType>(args...);
}

Context::Context()  { std::cout << "prepare\n"; }
Context::~Context() { std::cout << "done\n"; }
MSalters
  • 173,980
  • 10
  • 155
  • 350
  • This solution has already been posted here: http://stackoverflow.com/a/42725991/4000694 and as I said it is feasible, though I don't like it too much because it makes the code less readable. Anyway thank you for your hint. – HappyCactus Mar 15 '17 at 10:46
  • @HappyCactus: That's actually half of this solution: it covers the destructor part, but not the constructor. – MSalters Mar 15 '17 at 10:48