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...
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With