Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Invalid void parameter when combining template parameters to form a function signature

Trying to take the signatures of two callbacks and generate a callback signature that uses each of their return values.

Given Callbacks A and B => Generate F

Ex 1) A: int(char) B: double(bool) => F: double(int)

Ex 2) A: void(char) B: void(int) => F: void(void)

Ran into a strange compiler error when instantiating a callback with void as a parameter:

error: invalid parameter type ‘void’

Problematic Code

template<class Signature>
struct my_func;

template<class Ret, class... Args>
struct my_func<Ret(Args...)>
{};

template<class FuncA, class FuncB>
struct my_fwd;

template<class ORet, class... OArgs,
         class Ret, class... Args>
struct my_fwd<
  my_func<ORet(OArgs...)>,
  my_func<Ret(Args...)>
  >
{
  my_func< ORet(Ret) > func;  // <--- error
};

int main(int, char *[])
{
  my_func<void(int)> my3;  // (1)
  my_func<void(void)> my4; // (2)
  my_func<void()> my5;     // (3)

  my_fwd< decltype(my3), my_func<void(char)> > fwd1; // (4)
  my_fwd< decltype(my3), decltype(my4) > fwd2;       // (5)

  return 0;
}

Though there are no problems with the instantiation of my_func's with void (1), (2), (3), the my_fwd's (4) (5) fail, and I would like to understand why.

Workaround?!

I've found a workaround, by specializing my_fwd for Ret == void:

// with this specialization i can avoid the error
template<class ORet, class... OArgs,
         class... Args>
struct my_fwd<
  my_func<ORet(OArgs...)>,
  my_func<void(Args...)>
  >
{
  my_func< ORet() > func;
};

Question

What's the difference between the compiler trying to instantiate inside

my_fwd< my_func<void(int)>, my_func<void(char)> >:

-> my_func<void(void)> func

and

the manual version in main(): my_func<void(void)> my4?

Was the void specialization the correct approach for a fix? Alternatives? I'm obviously not excited about specializations and code duplication.

like image 773
assem Avatar asked Sep 12 '25 23:09

assem


1 Answers

The difference between instatiating with void as a template parameter and manually writing void (void) is that the latter does not produce a function taking a void parameter. Listing (void) as a function's parameters is a syntactic construct whose meaning is the same as (). It's a legacy from C, where () means "parameters are unspecified" and (void) means "no parameters." C++ removed the unspecified case, and () means "no parameters" there.

However, template instantiation happens long after syntactic processing, so actually trying to instantiate a function's parameters (T) with T = void results in an error. Just as it would be an error to try to declare a function taking (std::remove_reference<decltype(std::declval<void*>())>::type) (i.e. void actually spelled out as a type).

I am afraid the only way you can solve this is indeed by specialising for void.

like image 162
Angew is no longer proud of SO Avatar answered Sep 15 '25 12:09

Angew is no longer proud of SO