当T==void时,如何最好地解决“void foo(const T& t = T())”问题

3
我有一个函数,其中有一个类型为 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解决了这么多问题!一个更简洁的语法会更好...

2
如果 Tvoid,那么 t.stuff() 应该做什么?看起来我们应该希望编译器出现错误。你真的打算调用 foo 时不执行任何操作吗?如果是这样,也许我们应该重新审视你的设计。 - AndyG
3个回答

4

一个更简单的解决方案(只有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() {}

第二个版本非常适合有许多函数的用例。我一直在尝试实现这个,但我的语法有误,所以感谢您帮助解决了这个问题 :) 还有其他只有一个函数定义的答案,但它们仍然很冗长,难以阅读和使用,因此我会接受这个答案,因为它清晰而简洁。谢谢。 - Crog

2

两个默认的模板参数就可以搞定:

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

示例.

int&T的默认值,因此在尝试调用foo()时(取消在示例中的注释),如果没有模板参数或实际参数,则会编译失败(在U()处默认构造引用)。

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

如果您想要更加高级(并防止误用),可以添加可变守卫并使用闭包类型:

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 - 这可能是不必要的。

感谢您提供出色的答案。第一个版本解决了使用情况,但是对于应用于许多功能而言相当冗长。如果有一种语法可以缩写复制粘贴所涉及的复杂性并减少错误机会,那将是不错的选择!“华丽”版本需要使用-std = C ++ 2a功能,很好看,但我真的不清楚这样做可以/将避免什么误用? - Crog
当然,我已经尽可能地缩短了它 - 你需要为第二个模板参数创建某种别名模板。我还添加了“高级”功能/技术的解释。 - ecatmur

1
理想情况下,我希望有一种更优雅的解决方案来重载“Void”函数,而不必求助于第三个声明?特别是在新的C++17中,这些问题都得到了解决!更简洁的语法可能会更好...... 嗯...没有第三个声明,是的(您只能使用一个)。 更优雅...这是一个品味问题,我想。 更简洁的语法...嗯...几乎相同,我想。 无论如何,我提出以下版本,基于“if constexpr”和“std :: 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; */ }
 }

1
这个问题在于必须提供 T(而 U 可能会被推导为其他类型)。当然,如果从未像 foo(value);foo<base_class>(derived_object); 这样调用它,则不是问题。 - Artyer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接