禁止用返回常量引用的虚方法覆盖一个返回非常量引用的方法

8

以下代码可以编译:

struct Ret {};

struct A
{
    virtual const Ret& fun() = 0; 
};

struct B : public A
{
    Ret& fun() override
    {
        static Ret ret;
        return ret;
    }
};

int main()
{
    B b;
}

如何在编译时禁止重载方法返回值的const限定符与实际返回类型不一致?

提前感谢。


3
为什么您想禁止它?使用“A”接口的代码仍将获得一个const引用。 - StoryTeller - Unslander Monica
2
const Ret& fun() { return fun_internal(); } protected: virtual const Ret& fun_internal() = 0; 我不确定你能完全防止它... - Borgleader
我怀疑这个有用吗,但你可以使用CRTP检查一下:http://coliru.stacked-crooked.com/a/aadc705b9ce67dc6 - Borgleader
2
为什么这对你来说是个问题?它在任何方面都不会不安全。相反的情况才是问题,但那不会编译。 - molbdnilo
嘿,感谢回复!我原本以为,由于我无法像这样用返回整数的函数覆盖fun:int fun() override - 显然它是不同的类型,它根本不应该工作,非const限定符变体也不应该编译。也许它是相同的类型,但通过返回const引用,我限制了返回的对象引用调用自己的非const方法。我希望未来的实现者保留这种行为。现在只有使用A接口时才保留此行为。整个事情有点不一致。 - flamasterrr
1个回答

5

以下所有标准参考都指向N4659:2017年3月后柯纳工作草案/C++17 DIS


派生函数的返回类型需要与其重写的函数的返回类型协变,但反过来不需要

[class.virtual]/7所规定,如下所示,强调部分由本人添加:

一个重写函数的返回类型必须是与被重写函数的返回类型相同或者协变的。如果函数 D::f 重写了函数 B::f,则函数的返回类型在满足以下条件时具有协变性:

  • [...]
  • (7.3) 如果两个指针或引用具有相同的 cv-限定符,并且函数 D::f 返回类型中类类型的cv-限定符与函数 B::f 返回类型中的类类型相同或更少,则它们具有协变性。

这也就意味着以下程序可以编译通过。

struct Ret {};

struct A {
    virtual const Ret& fun() = 0;
};

struct B : public A {
    Ret& fun() override { /* ... */ }
};

int main() {}

我们需要注意的是,对于基础类型为B的对象的A::fun接口的多态使用将强制执行接口的返回类型的常量性质。而以下程序是不合法的:

struct Ret {};

struct A {
    virtual Ret& fun() = 0;
};

struct B : public A {
    const Ret& fun() override { /* ... */ }
};

int main() { }

这会伴随着以下指导性的编译器错误信息(Clang)

error: return type of virtual function 'fun' is 
       not covariant with the return type of the 
       function it overrides

我们可以自然地发现,如果接口A允许多态调用非常量Ret&返回类型的fun(),即使派生对象实现该重载函数返回const Ret&,这将导致通过多态修改const对象的行为未定义。


当然有解决方法(例如用Curiosly Recurring Template Pattern替换动态多态,并在注入到基类中的派生类型上添加constness断言),但这些解决方法都似乎在解决一个XY问题,并且很可能会实现只增加代码复杂性而没有明显收益的模式。


我很惊讶这里也涵盖了 class B::fun() 返回一个从 Ret 派生的类型的情况:在 coliru 上测试 - Scheff's Cat
1
@Scheff 是的,这是由 [class.virtual]/7.2 管理的(在我上面的摘录中省略了): "[...] 如果函数的返回类型满足以下条件,则函数的返回类型是协变的:[...] (7.2) B::f 的返回类型中的类与 D::f 的返回类型中的类相同,或者是 D::f 返回类型中的类的一个明确且可访问的直接或间接基类" - dfrib

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