4

Consider

#include <tuple>

template <typename... F>
auto execute (F... f) {
    return std::make_tuple(f(0)...);
}

int foo(int) { return 5; }
int bar(int) { return 3; }

int main() {
    auto tuple = execute(foo, bar);
}

What is a good workaround so that bar can return void?

I tried this, but it won't compile:

#include <tuple>

struct Void { };

template <typename T>
T check(T n) { return n; }

Void check(void) { return Void{}; }

template <typename... F>
auto execute (F... f) {
    return std::make_tuple(check(f(0))...);
}

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

int main() {
    auto tuple = execute(foo, bar);
}

Update: I have a tentative solution, but it only works if we know that the arguments passed is always int 0. I'll try to make it work in any general setting. Or perhaps use std::optional like Steve suggested.

#include <tuple>
#include <type_traits>

struct Void { };

template <typename F>
auto execute_h (F f, std::enable_if_t<!std::is_void_v<std::result_of_t<F(int)>>>* = nullptr) {
    const auto result = f(0);
    return result;  
}

template <typename F>
auto execute_h (F f, std::enable_if_t<std::is_void_v<std::result_of_t<F(int)>>>* = nullptr) {
    f(0);
    return Void{};  
}

template <typename... F>
auto execute (F... f) {
    return std::make_tuple(execute_h(f)...);
}

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

int main() {
    auto tuple = execute(foo, bar);
}
prestokeys
  • 4,817
  • 3
  • 20
  • 43
  • If you're using C++17, how about std::optional? – Steve Nov 16 '17 at 22:19
  • Related to [applying-a-function-to-each-element-of-a-tuple](https://stackoverflow.com/questions/47153933/applying-a-function-to-each-element-of-a-tuple). – Jarod42 Nov 16 '17 at 22:22

2 Answers2

4

With abusing overload of operator , (as void(), T won't call custom operator,):

#include <tuple>

struct Void {};

template <typename T>
T&& operator , (T&& t, Void) { return std::forward<T>(t); }

template <typename... Fs>
auto execute (Fs... fs) {
    return std::make_tuple((f(0), Void{})...);
}

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

int main() {
    auto tuple = execute(foo, bar); // tuple<int, Void>
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302
2

You can use std::enable_if and a wrapper function to return a Void object for functors that return void:

#include <tuple>
#include <type_traits>

struct Void { };

template <typename Func, typename... Args>
auto check(Func func, Args&&... args)
    -> std::enable_if_t<!std::is_void<decltype(func(std::forward<Args>(args)...))>::value, decltype(func(std::forward<Args>(args)...))>
{
    return func(std::forward<Args>(args)...);
}

template <typename Func, typename... Args>
auto check(Func func, Args&&... args)
    -> std::enable_if_t<std::is_void<decltype(func(std::forward<Args>(args)...))>::value, Void>
{
    func(std::forward<Args>(args)...); return Void{};
}

template <typename... F>
auto execute (F... f) {
    return std::make_tuple(check(f, 0)...);
}

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

int main() {
    auto tuple = execute(foo, bar);
}

Live demo

It's a bit repetitive, but it gets the job done for any type of functor, any argument list, and any return type.

It's worth noting that there's a proposal floating around to make void a regular type that would let you avoid all of these hassles, but I'm not sure what the status of that is or if it will ever be accepted.

Miles Budnek
  • 28,216
  • 2
  • 35
  • 52