Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to best solve "void foo( const T& t = T() )" when T==void

I have a function which has an option parameter of type T.

template<typename T>  
void foo( const T& t = T() )  
{ t.stuff; }

This was all fine but I now have a scenario where T becomes void. In this case I expected a no-operation empty function. The only workable solution I have requires three separate declarations and I have many of these sort of methods:

template<typename T> 
void foo( const T& t)
{ t.stuff; }

template<typename T>
inline void foo() 
{ foo(T()); }

template<> 
inline void foo<void>() {}

Ideally I hope there should be a more elegant solution to overload the 'Void' function without resorting to a 3rd declaration? Especially with new C++17 solving so many things these days! A more concise syntax that was shorter coudl be nice...

like image 749
Crog Avatar asked Nov 30 '25 08:11

Crog


2 Answers

A simpler solution (in that there are only 2 overloads) would be something like this:

template<typename T>
void foo( const T& t = T() ) {
    t.stuff;
}

template<typename T>
std::enable_if_t<std::is_void_v<T>>
foo() {}

// void foo(const T&); is the only viable overload for non-void T,
// since std::enable_if_t SFINAEs

// void foo(); is the only viable overload for void T,
// since const T& forms a reference to void

And this can be slightly shortened with alias templates since you use this pattern a lot:

template<typename T, typename TypeIfVoid = void>
using if_void = std::enable_if_t<std::is_void_v<T>, TypeIfVoid>;


template<typename T>
void foo(const T& t = T()) {
    t.stuff;
}

template<typename T>
if_void<T> foo() {}
like image 102
Artyer Avatar answered Dec 01 '25 22:12

Artyer


Two default template parameters will do it:

template<class T> using FallbackT = std::conditional_t<std::is_void_v<T>, int, T>;

template<class T = int&, class U = FallbackT<T>>
void foo(U const& t = U()) {
    if constexpr (!std::is_void_v<T>)
        t.stuff();
}

Example.

int& is the default for T so that compilation fails (default-constructing a reference at U()) if someone attempts to call foo() without either a template argument or an actual argument (try uncommenting it in the example).

I'm using int within FallbackT alias template since U just needs to be something that is default-constructible - this isn't visible to the user.

If you want to be fancy (and prevent misuse) you could add a variadic guard and use closure types:

template<
    class T = decltype([]{})&,
    class...,
    class U = std::conditional_t<std::is_void_v<T>, decltype([]{}), T>>
void foo(U const& t = U()) {
    if constexpr (!std::is_void_v<T>)
        t.stuff();
}

Here, the variadic guard prevents specifying U explicitly, as e.g. foo<int, long>(); the closure types make it impossible for someone to call foo with those types by any other means - this is probably unnecessary.

like image 24
ecatmur Avatar answered Dec 01 '25 21:12

ecatmur