Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling a void variable in a templatized function in C++11

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).

like image 732
HappyCactus Avatar asked Mar 10 '17 18:03

HappyCactus


People also ask

Can a variable be void in C?

A void* pointer can be converted into any other type of data pointer. In C++, a void pointer can point to a free function (a function that's not a member of a class), or to a static member function, but not to a non-static member function. You can't declare a variable of type void .

How do you use a void function?

Void functions are created and used just like value-returning functions except they do not return a value after the function executes. In lieu of a data type, void functions use the keyword "void." A void function performs a task, and then control returns back to the caller--but, it does not return a value.

What is typename in C++ template?

" typename " is a keyword in the C++ programming language used when writing templates. It is used for specifying that a dependent name in a template definition or declaration is a type.

What is the correct syntax of defining function template template functions?

Defining a Function TemplateA function template starts with the keyword template followed by template parameter(s) inside <> which is followed by the function definition. In the above code, T is a template argument that accepts different data types ( int , float , etc.), and typename is a keyword.


1 Answers

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".

like image 53
Vittorio Romeo Avatar answered Sep 28 '22 09:09

Vittorio Romeo