模板化向量函数参数中的std::is_base_of是什么意思?

3
我希望提供两种不同的operator>>实现,取决于给定类型是否是特殊类型的子类:
class A {};

class B : public A{};
class C {};

template<typename T>
std::istream& operator>>(std::istream& is,
    std::vector<typename std::enable_if<std::is_base_of<A, T>::value>::type>& vec)
{
    std::cout << "Special case called" << std::endl;
    return is;
}

template<typename T>
std::istream& operator>>(std::istream& is,
    std::vector<T>& vec)
{
    std::cout << "General case called" << std::endl;
    return is;
}


void main(int argc, char **argv)
{
    std::vector<A> a;
    std::vector<B> b;
    std::vector<C> c;
    std::stringstream ss("A string");
    ss >> a;
    ss >> b;
    ss >> c;
}

打印哪些内容

General case called
General case called
General case called

将第二个运算符定义更改为:
template<typename T>
std::istream& operator>>(std::istream& is,
    std::vector<typename std::enable_if<!std::is_base_of<A, T>::value>::type>& vec)
{
    std::cout << "General case called" << std::endl;
    return is;
}

因为以下原因,无法编译:

error C2678: binary '>>' : no operator found which takes a left-hand operand of type 'std::stringstream'

我可能在错误地使用std::enable_if。但是正确的用法是什么?这里的模板化std::vector是否有问题?

2个回答

8

我认为在这里使用std::enable_if可能不是最理想的位置,我会将其放在返回类型中以启用SFINAE:

template<typename T>
typename std::enable_if<std::is_base_of<A, T>::value,std::istream>::type& 
operator>>(std::istream& is,std::vector<T>& vec)
{
    std::cout << "Special case called" << std::endl;
    return is;
}

template<typename T>
typename std::enable_if<!std::is_base_of<A, T>::value,std::istream>::type& 
operator>>(std::istream& is,std::vector<T>& vec)
{
    std::cout << "General case called" << std::endl;
    return is;
}

live demo


非常感谢,这解决了问题!我有一个简短的跟进问题:我可以提供第三个明确的专业化吗? “template <> std :: istream&operator>>(std :: istream&is,std :: vector <std :: string>&vec){...}”无法编译,因为“错误C2794:'type':不是任何直接或间接基类的成员 'std :: enable_if <false,std :: istream>'”。 - PhilLab
3
@PhilLab 偏向于使用函数重载而非函数特化。 - W.F.
哎呀,我错过了显而易见的事情 :-)。如果你只拥有一把锤子,那么所有东西都会看起来像钉子。 - PhilLab

2

@Biggy的回答正确地演示了一种正确的方法。这里有一个替代方案,我认为更易读(以及一些小的修正和改进):

template<typename T, typename std::enable_if<std::is_base_of<A, T>{}, int>::type = 0>
std::istream& operator >>(std::istream& is, std::vector<T>& vec) {
    std::cout << "Special case called\n";
    return is;
}

template<typename T, typename std::enable_if<!std::is_base_of<A, T>{}, int>::type = 0>
std::istream& operator >>(std::istream& is, std::vector<T>& vec) {
    std::cout << "General case called\n";
    return is;
}

在线演示

或者更好的方式是,使用标签派发:

template<typename T>
std::istream& impl(std::istream& is, std::vector<T>& vec, std::true_type) {
    std::cout << "Special case called\n";
    return is;
}

template<typename T>
std::istream& impl(std::istream& is, std::vector<T>& vec, std::false_type) {
    std::cout << "General case called\n";
    return is;
}

template<typename T>
std::istream& operator >>(std::istream& is, std::vector<T>& vec) {
    return impl(is, vec, std::is_base_of<A, T>{});
}

在线演示

在实践中,当条件数量较多时,我发现后一种方法更易于维护,并且可以产生更易于解读的错误消息。

至于为什么你的代码不起作用,问题在于std::enable_if<std::is_base_of<A,T> ::value> ::type作为参数类型不是对于T可推导的上下文,并且没有涉及到T的其他参数来帮助推断。因此,因为您在调用它时没有明确指定T,所以该运算符根本不是可调用的候选项,因为T是未知的。当您将相同的尝试SFINAE放入另一个重载中时,两个重载都不可行,因此会出现错误。一个粗略的规则是:如果您需要typename(与enable_if的用法一样),则您不再处于可推导的上下文中。

enable_if放在返回类型或默认模板参数中有效的原因是参数类型(特别是漂亮而简单的std::vector<T>&)是可推导的,因此T是可推导的。


非常感谢您的解释,特别是为什么它没有起作用。 - PhilLab

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