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

Viewed 146

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...

3 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() {}

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.

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...

Well... without a 3rd declaration, yes (you can use only one).

More elegant... it's question of tastes, I suppose.

More coincise syntax... well... almost the same, I suppose.

Anyway, I propose the following version, if constexpr and std::conditional_t based.

template <typename T,
          typename U = std::conditional_t<std::is_same_v<T, void>,
                                          int,
                                          T>>
void foo (U const & u = U{})
 {
   if constexpr ( std::is_same_v<T, void> )
    { /* do nothing */ }
   else
    { /* do u.stuff; */ }
 }
Related