如何在 T==void 时最好地解决 "void foo( const T& t = T() )"

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

我有一个函数,它有一个类型为 T 的选项参数。

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

这一切都很好,但我现在遇到了 T 变成 void 的情况。在这种情况下,我希望有一个无操作的空函数。我唯一可行的解​​决方案需要三个单独的声明,而且我有很多这样的方法:

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

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

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

理想情况下,我希望应该有一个更优雅的解决方案来重载 'Void' 函数而不诉诸第三个声明?特别是现在新的 C++17 解决了很多问题!更简洁、更短的语法会更好...

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

嗯...没有第三个声明,是的(你只能使用一个)。

更优雅...我想这是品味问题。

更准确的语法...好吧...几乎相同,我想。

无论如何,我建议以下版本,if constexprstd::conditional_t基础。

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; */ }
 }

一个更简单的解决方案(因为只有 2 个重载)是这样的:

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

由于您经常使用此模式,因此可以使用别名模板稍微缩短它:

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

两个默认模板参数即可:

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&T 的默认值,因此如果有人试图调用 foo() 时编译失败(default-constructing 在 U() 处的引用)模板参数或实际参数(尝试在示例中取消注释)。

我在 FallbackT 别名模板中使用 int,因为 U 只需要是 default-constructible 的东西 - 这对用户不可见。

如果你想花哨(并防止误用),你可以添加可变参数保护并使用闭包类型:

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();
}

在这里,可变参数守卫阻止明确指定 U,例如foo<int, long>();闭包类型使得某人无法通过任何其他方式使用这些类型调用 foo - 这可能是不必要的。