Using 'Void' Template Arguments in C++

Using 'void' template arguments in C++

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

Template parameter type `void` vs explicit use of `void`

The rule saying a parameter list (void) is the same as the empty parameter list () is found in C++ Standard [dcl.fct]/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.

The important piece for this question is "non-dependent type". The template parameter T is type-dependent, so it doesn't trigger the rule.

I presume the Standard has this rule because it would also be quite confusing (I'd say worse) if a template function which normally takes one parameter could suddenly become a zero-parameter function if an instantiation happens to make that parameter type void. With this rule, we know when a template is declared with a parameter, it really has one parameter.

C++: use void as template argument

The quickest way of solving this without explicitly specializing for void is to use a parameter pack (added in C++11) for your template argument instead of a single type and using an empty parameter pack instead of void. A parameter pack can homogeneously hold any number of type, including 0 and 1. Then it can be used to generate the right types and member functions. You basically just have to add ... correctly near every use of Arg (link) :

#include <functional>
#include <iostream>

template<typename ... Arg>
class Event
{
public:
using Callback = std::function<void(const Arg&...)>;

Event(Callback c) : mCallback(c){}

void Trigger(const Arg& ... arg) {
mCallback(arg...);
}

private:
Callback mCallback;
};

static void FooVoid() {
std::cout << "Look ma, no args!" << std::endl;
}

static void FooInt(int a) {
std::cout << "int arg " << a << std::endl;
}

int main()
{
/* Compiles */
Event<int> eInt(&FooInt);
eInt.Trigger(42);

Event<> eVoid(&FooVoid);
eVoid.Trigger();

return 0;
}

This has the added benefit that you can use callbacks with more than one argument. If this isn't desirable you can add a static_assert to prevent it :

template<typename ... Arg>
class Event
{
public:
using Callback = std::function<void(const Arg&...)>;
static_assert(sizeof...(Arg) <= 1, "Too many arguments");

Event(Callback c) : mCallback(c){}

void Trigger(const Arg& ... arg) {
mCallback(arg...);
}

private:
Callback mCallback;
};

Notice that this solution requires Event<> instead of Event<void>. You can solve that by adding a short specialization for Event<void> that uses Event<> (link) :

template<>
class Event<void> : public Event<>
{
// Inherit constructors
using Event<>::Event;
};

How to pass a template function as an argument in C++?

You need to specify the template argument for display explicitly:

iter(arr, 3, display<int>);

Or make iter taking function pointer:

template <class T_arr>
void iter(T_arr *arr, int len, void (*func)(T_arr))
{
for(int i = 0; i < len; i++)
{
func(arr[i]);
}
}

then you can

iter(arr, 3, display); // template argument gets deduced as int

c++11: void in template arguments

do I have to write partial specification for my_function<void(Input...)>...

As written, yes. You are saving the return value from your call in a variable, so there's no way around that. However, you could simply not write your function_wrapper that way, and have operator() simply return:

Return operator()() { return somehow_manipulate(input_args); }

You're allowed to write code like that even if Return is void and somehow_manipulate is a void function. That's how std::function<R(Args...)> is implemented. There's no special case for void.

why C++ standard does not allow to define void variables?

Because void is not a value. A void function doesn't return something of type void, it doesn't return anything. It's uniquely nothing. It is emptiness. It is the Buddhist ideal. You cannot have something of nothing.

How to pass void arguments in a class/function templates

You can specify a template specification of type void, for example, you could use the following variations of the templated class, button:

template <typename rtnVal, typename Val1, typename Val2>
class Button {
private:
rtnVal(*Function)( Val1 val1, Val2 val2 );

public:
Button() : Function( nullptr ) {}

void SetFunction( rtnVal(*func)(Val1, Val2) ) {
Function = func;
}

rtnVal RunFunction( Val1 val1, Val2 val2 ) { return Function( val1, val2 ); }
};

// Special void type, accepting arguments overload:
template < typename Val1, typename Val2 >
class Button< void, Val1, Val2 > {
private:
void(*Function)(Val1 val1, Val2 val2);

public:
Button() : Function( nullptr ) {}

void SetFunction( void(*func)(Val1, Val2) ) {
Function = func;
}

void RunFunction( Val1 val1, Val2 val2 ) { return Function( val1, val2 ); }

};

// Pure void type:
template<>
class Button<void, void, void> {
private:
void(*Function)( void );

public:
Button() : Function( nullptr ) {}

void SetFunction( void(*func)() ) {
Function = func;
}

void RunFunction() {
return Function();
}
};

This then allows you to initialize and use void as arguments, for example, given a void function Print() the following would now be valid:

void Print()
{
std::cout << "Function has been called" << std::endl;
}

int main()
{
Button< void, void, void > btn;

btn.SetFunction( Print );

btn.RunFunction();

std::cout << "Finished";
}

I hope this helps to clear things up! :)

Note: nullptr is a C++0x keyword, if your compiler hasn't implemented it use #define nullptr 0

Determine at compile time if argument type is void

With specialization, you might do something like:

template<typename MethodType>
struct FunctionWrapper;

// 1 arg
template<typename Class, typename ArgType>
struct FunctionWrapper<void (Class::*)(ArgType /*, ...*/) /* const volatile noexcept & && */>
{
using Function = void (Class::*)(ArgType);
FunctionWrapper(Function func) : func_(func) {}
Function func_;
};

// No args
template<typename Class>
struct FunctionWrapper<void (Class::*)(/*...*/) /* const volatile noexcept & && */>
{
using Function = void (Class::*)();
FunctionWrapper(Function func) : func_(func) {}
Function func_;
// special stuff.
};

In your case, you might check if method is invocable, and get rid of your extra template parameter:

template <typename FunctionWrapperType>
struct THE_PROXY {
THE_PROXY(FunctionWrapperType* wrapper) : wrapper_(wrapper) {}

template<typename T>
THE_PROXY& operator=(T val) { wrapper_->Set(val); return *this; }

private:
FunctionWrapperType* wrapper_;
};

template<typename Function, typename ContainingClass>
struct FunctionWrapper {

FunctionWrapper(Function func, ContainingClass* c) : func_(func), containingClass_(c) {}

THE_PROXY<FunctionWrapper> operator*() { return THE_PROXY(this); }

private:
template<class T> friend struct THE_PROXY;

template<typename Arg>
void Set(Arg arg)
{
if constexpr (std::is_invocable_v<Function, ContainingClass, Arg>) {
std::invoke(func_, containingClass_, arg);
} else {
void* address_to_write_to = std::invoke(func_, containingClass_);
memcpy(address_to_write_to, &arg, sizeof(Arg));
}
}

Function func_;
ContainingClass* containingClass_;
};

Demo

Use of void template argument in early detection idiom implementation

Judging on how the authors wrote their final implementation of is_detected, they intended that Op be a variadic template, which allows one to express many more concepts:

(Also pulled from n4502)

// primary template handles all types not supporting the archetypal Op:
template< class Default
, class // always void; supplied externally
, template<class...> class Op
, class... Args
>
struct
detector
{
using value_t = false_type;
using type = Default;
};
// the specialization recognizes and handles only types supporting Op:
template< class Default
, template<class...> class Op
, class... Args
>
struct
detector<Default, void_t<Op<Args...>>, Op, Args...>
{
using value_t = true_type;
using type = Op<Args...>;
};
//...
template< template<class...> class Op, class... Args >
using
is_detected = typename detector<void, void, Op, Args...>::value_t;

When you get into a scenario like this, a void becomes necessary so that template specialization will match the true_type version when Op<Args...> is a valid expression.

Here's my tweak on the original detect to be variadic:

// primary template handles all types not supporting the operation:
template< class T, template<class...> class Trait, class... TraitArgs >
struct
detect : std::false_type { };
// specialization recognizes/validates only types supporting the archetype:
template< class T, template<class...> class Trait, class... TraitArgs >
struct
detect< T, Trait, std::void_t<Trait<T, TraitArgs...>>, TraitArgs... > : std::true_type { };

template<class T, template<class...> class Trait, class... TraitArgs>
using is_detected_t = typename detect<T, Trait, void, TraitArgs...>::type;

template<class T, template<class...> class Trait, class... TraitArgs>
constexpr bool is_detected_v = detect<T, Trait, void, TraitArgs...>::value;

Note that I renamed Op to Trait, Args to TraitArgs, and used std::void_t which made it into C++17.

Now let's define a trait to test for a function named Foo that can may or may not accept certain parameter types:

template<class T, class... Args>
using HasFoo_t = decltype( std::declval<T>().Foo(std::declval<Args>()...));

Now we can get a type (true_type or false_type) given some T and our trait:

template< class T, class... Args>
using has_foo_t = is_detected_t<T, HasFoo_t, Args...>;

And finally, we can also "just check" to see if the trait is valid for some provided T and Args:

template<class T, class... Args>
constexpr bool has_foo_v = is_detected_v<T, HasFoo_t, Args...>;

Here's a struct to start testing:

struct A
{
void Foo(int)
{
std::cout << "A::Foo(int)\n";
}
};

And finally the test(s):

std::cout << std::boolalpha << has_foo_v<A, int> << std::endl; //true
std::cout << std::boolalpha << has_foo_v<A> << std::endl; // false

If I remove the void from my is_detected_t and is_detected_v implementations, then the primary specialization is chosen, and I get false (Example).

This is because the void is there so as to match std::void_t<Trait<T, TraitArgs...>> which if you recall will have a type of void if the template argument is well-formed. If the template argument is not well-formed, then std::void_t<Trait<T, TraitArgs...>> is not a good match and it will revert to the default specialization (false_type).

When we remove void from our call (and simply leave TraitArgs... in its place) then we cannot match the std::void_t<Trait<T, TraitArgs...>> argument in the true_type specialization.

Also note that if std::void_t<Trait<T, TraitArgs...>> is well-formed, it simply provides a void type to the class... TraitArgs argument in the primary template, so we don't need to define an extra template parameter to receive void.

In conclusion, the authors wanted to remove the void that would end up in client code, hence their slightly more complicated implementation later in the paper.

Thanks to @Rerito for pointing out this answer where Yakk also puts in a little extra work to avoid the pesky void in client code.

A template function as a template argument

You can't have a template template parameter that accepts function templates, only class templates. Luckily we can make a class that looks rather like a function.

#include <vector>

template <class T>
struct CopyVector { void operator()() { std::vector<T> v; /*...*/} };

template <class T>
struct CopyVectorAsync{ void operator()() { std::vector<T> v; /*...*/} };

template <template <class> class copy>
void Test()
{
copy<char>{}();
copy<short>{}();
copy<int>{}();
}

int main()
{
Test<CopyVector>();
Test<CopyVectorAsync>();
}


Related Topics



Leave a reply



Submit