使用和重载基类的模板成员函数

using and overloading a template member function of a base class?

本文关键字:成员 函数 基类 重载      更新时间:2023-10-16

在下面,结构Y重载X的成员函数f。这两个重载都是模板函数,但采用不同的参数(typenameint),以明确指定:

struct X
{
    template <typename> static bool f() { return true; }
};
struct Y : public X
{
    using X::f;
    template <int> static bool f() { return false; }
};
int main()
{
    std::cout << Y::f <void>() << " " << Y::f <0>() << std::endl;
}

这将使用 gcc 打印1 0,如预期的那样。然而,叮当(3.3)抱怨

[...] error: no matching function for call to 'f'
        std::cout << Y::f <void>() << " " << Y::f <0>() << std::endl;
                     ^~~~~~~~~~~
[...] note: candidate template ignored: invalid explicitly-specified argument
      for 1st template parameter
        template <int> static bool f() { return false; }
                                   ^

即只能看到Y的版本。我试过了

using X::template f;

相反,没有成功。非静态(模板)成员函数也会发生同样的情况。那么这是一个错误吗?

这个难题最近根据另一个答案向我解释了。

从 #clang IRC 频道:

[01:16:23] <zygoloid> Xeo: this is a weird corner of the language where clang conforms but the rule is silly
[01:16:31] <Xeo> ... really? :(
[01:16:45] <zygoloid> Xeo: when deciding whether a using-declaration is hidden, we're not allowed to look at the template-parameter-list (nor the return type, iirc)
[01:17:04] <zygoloid> so the derived class declaration of operator()(T) suppresses the using-declaration
[01:17:19] <Xeo> because it has the same signature / parameter types?
[01:17:40] <zygoloid> rigth

解决方法是不在uses生版本的类中定义f。相反,将其移动到辅助帮助程序类中(在这种情况下,这引出了一个问题,您认为哪个定义应该获胜)。

  • 请参阅此处了解我之前有问题的案例:Lambda 函数作为基类

    @Xeo知道为什么 clang++ 拒绝这个:coliru.stacked-crooked.com/a/6a0e6a1cac062216(叮当:coliru.stacked-crooked.com/a/5c2d6dd449a92227)

  • 以下是使用额外的基类修复它的方法:

    无论如何@Xeo修复了它,出于某种原因,这种形式对 clang++ stackoverflow.com/a/18432618/85371 并不反感

感谢@Xeo和休息室里的人挖掘出这个"愚蠢的规则"

令人非常失望的是,这样的约束存在并且在 C++11 中没有放松(可能有充分的理由,但我无法想象为什么)。我觉得它打败了阶级等级制度的整个概念。

无论如何,这是我找到的一种解决方法。我包含了另一个非静态的函数g来说明差异,因为这种情况是我的主要兴趣。

template <typename Y>
struct X
{
    template <typename> static bool f() { return true; }
    template <typename>        bool g() { return true; }
    template <int I>
    static bool f() { return Y::template _f <I>(); }
    template <int I>
    bool g()
    {
        return static_cast <Y&>(*this).template _g <I>();
    }
};
class Y : public X <Y>
{
    friend class X <Y>;
    template <int> static bool _f() { return false; }
    template <int>        bool _g() { return false; }
};
int main()
{
    Y y;
    std::cout << Y::f <void>() << " " << Y::f <0>() << std::endl;
    std::cout << y. g <void>() << " " << y. g <0>() << std::endl;
}

所以所有的重载都发生在基类X中,它通过将Y作为模板参数来实现静态多态性(幸运的是,在我的项目中已经是这种情况,所以我不改变设计)。

实际Y的实现是在私有函数_f_g。当有许多派生类(如 Y 类)中只有一个重载,而单个基类X有多个其他重载时,此设计很好。在这种情况下,可以避免大量代码重复。同样,在我的项目中就是这种情况。

X不需要知道这些函数的返回值。不幸的是,它确实需要知道返回类型:我已经尝试过例如 auto g() -> decltype(...),同样,这个decltype仅适用于 gcc。启用 c++1y 只写入没有尾随返回类型规范的auto g(),从而避免了decltype的问题。但是,clang对"正常函数的返回类型扣除"(N3638)的支持仅在当前的SVN版本中可用。

auto g()成为主流(和标准)之前,必须手动计算Y方法的返回类型,这可能会很痛苦,尤其是在有很多Y的情况下。

对我来说,它仍然看起来一团糟,但至少不是完整的。