Using 'void' template arguments in C++

40,982

Solution 1

The short answer is "templates are not string substitution". void f(void) has meaning only so far as it is an alias for void f() in C++, in order to be backwards compatible with C.

The first step is to use variadics, as noted elsewhere.

The second step is figuring out how to map void returning functions to ... well, maybe something like std::function<void()>, or maybe something else. I say maybe something else because unlike the other cases, you cannot call std::function<void()> foo; foo( []()->void {} ); -- it isn't a true continuation.

Something like this maybe:

template<typename T>
struct Continuation
{
  typedef std::function<void(T)> type;
};

template<>
struct Continuation<void>
{
  typedef std::function<void()> type;
};

then use it like this:

auto someFunc = []()->void {};
Continuation<decltype(someFunc())>::type c;

which gives you the type you want. You could even add in an apply to continuation:

template<typename T>
struct Continuation
{
  typedef std::function<void(T)> type;

  template<typename func, typename... Args>
  static void Apply( type const& cont, func&& f, Args... args)
  {
    cont( f(args...) );
  }
};

template<>
struct Continuation<void>
{
  typedef std::function<void()> type;
  template<typename func, typename... Args>
  static void Apply( type const& cont, func&& f, Args... args)
  {
    f(args...);
    cont();
  }
};

which lets you apply a continuation to an execution of a function uniformly if the incoming type is a void or if it is a non-void type.

However, I would ask "why would you want to do this"?

Solution 2

I don't have an actual answer, only what I said in the comment: You can't have void as a function type, as in:

int foo(int, char, void, bool, void, void);     // nonsense!

I believe that T(void) is only allowed as a compatibility notation for C (which distinguishes declarations and prototypes, very differently from C++, and which needs to be able to say "no arguments").

So, the solution should be variadic:

template <typename ...Args> using myType = std::function<void(Args...)>;

That way you can properly have no arguments:

myType<> f = []() { std::cout << "Boo\n"; }

Solution 3

Several answers already explain the rationale. To add to those answers, the specification says (C++11 §8.3.5[dcl.func]/4):

A parameter list consisting of a single unnamed parameter of non-dependent type void is equivalent to an empty parameter list. Except for this special case, a parameter shall not have type cv void.

In your Type2 example, the T in void(T) is a dependent type--it depends on a template parameter.

Solution 4

When a function is declared to take a parameter of type void, as in std::function<void(void)>, that is really just a goofy way of saying that it takes zero parameters. But the way you've declared Type2 is as a std::function with a signature that returns nothing (void), but that takes 1 parameter. void is not a type that can be used as a parameter, it is just a way of declaring that there are no parameters. So it doesn't work with Type2, because that requires an actual type that can be used as a parameter.

Share:
40,982

Related videos on Youtube

Nick Hutchinson
Author by

Nick Hutchinson

London-based Software Developer.

Updated on August 13, 2020

Comments

  • Nick Hutchinson
    Nick Hutchinson over 3 years

    Take the following minimal example:

    using Type1 = std::function<void(void)>;
    
    template <typename T>
    using Type2 = std::function<void(T)>;
    
    Type1 whyDoesThisWork;
    Type2<void> andYetThisDoesNot;
    

    If the second type alias, I get the error "Argument may not have 'void' type". (I tested with Xcode 4.5, Clang/c++11/libc++, OS X 10.7.)

    I find this curious: I would have expected Type1 and Type2<void> to behave identically. What's going on here? And is there a way to rewrite the second type alias so I can write Type2<void> and get std::function<void(void)> instead of an error?

    Edit I should probably add that the reason I want this is to allow for something like the following:

    template <typename ... T>
    using Continuation = std::function<void(T...)>;
    
    auto someFunc = []() -> void {
      printf("I'm returning void!\n");
    };
    
    Continuation<decltype(someFunc())> c;
    

    Continuation<decltype(someFunc())> becomes Continuation<void> and I get the error.

    • Kerrek SB
      Kerrek SB over 11 years
      Hehe, it works the other way round (T(void))...
    • Kerrek SB
      Kerrek SB over 11 years
      If I had to guess, I'd say that void(void) is just a courtesy compatibility notation for void(), but void isn't actually a valid argument type.
  • Nick Hutchinson
    Nick Hutchinson over 11 years
    Thanks, that pointed me in the right direction. Why would I want do do this? I'm trying to implement a simple version of Futures from Microsoft's Task Parallel Library. A continuation receives as arguments the return values of the tasks it depends on.
  • Gerald
    Gerald over 10 years
    Sorry, I somehow managed to down-vote this by accident and didn't notice until after it was too late to change it.
  • Artyer
    Artyer over 3 years
    This also means that non-dependent types like void(typename std::type_identity<void>::type) and void(std::void_t<T>) also work which is strange