【问题标题】:Templating (or somehow autoing) the return value of methods模板化(或以某种方式自动)方法的返回值
【发布时间】:2011-10-12 02:50:28
【问题描述】:

编辑:要明确——马上——这是一个关于现代 C++ 编译器的语言能力的问题。不是关于特定目标的问题。如果不先澄清这一点,就很难描述这样一个抽象的概念,我已经意识到一些混乱围绕着通常做的事情而不是可能做的事情。这是一个非常抽象的问题。这里没有任何东西可以编译,这是故意的。同样,我不是在问如何使这个特定案例工作,而是在问是否有办法让 C++ 识别我想做的事情(通过模板或某种 auto->decltype 技巧最有可能如果甚至可能)。

我对 C++ 并不完全陌生,但肯定不是专家。自从我重新发现语言的力量以来,这是我一直在努力解决的一个基本问题。这里的最终目标是优雅地(并且使用尽可能少的代码)基于调用上下文转发正确的多态返回值。比如……

class A {
    public:
        A& foo() {
            // do something mutant fooish
            return *this;
        };
};

class B: public A {
    public:
        B& bar() {
            // do something mutant barish
            return *this;
        };
};

int main(int argc, char** argv) {
    B yarp;
    yarp.foo().bar();
};

编译错误。有道理,C++ 旨在假设您对自己正在做的事情一无所知(这使得它可以高度优化,但有时会很痛苦……一种中高级 OOP 语言)。

显然,C++ 编译器已经到了这样的地步,他们不仅知道您要求什么(A().foo() 有效,B().foo() 有效),而且还知道您要求什么上下文中您要求它(因此在 C++11 中 auto yarp = B(),编译器知道 yarp 是 B 的一个实例)。有没有办法优雅地利用这一点,而不必重现一堆“使用”语句或包装方法(奇怪的是,它们没有根据 gcc 二进制文件的反汇编得到优化)?

那么这里有什么技巧吗?我根本没有在网上学到的东西。 auto -> decltype 技巧还是模板技巧?示例:

class A {
    public:
        template <typename R>
        R& foo() {
            // do something fooish
            return (R&)*this;
        };
};

class B: public A {
    public:
        using A::foo<A>; // << even this would be better than nothing (but no where near  optimum)
        B& bar() {
            // do something barish
            return *this;
        };
};

更简单的东西?如果您将此概念扩展到用于引用计数和 gc 释放的代理模板类的运算符,那么很明显这会产生多大的问题。提前感谢您的任何帮助(哦,第一次在 stackoverflow 上发帖,所以如果我有任何格式错误或您有更好的结构化帖子的建议,请道歉并请指出)。

【问题讨论】:

  • “C++ 旨在假设您对自己在做什么一无所知”,实际上,情况恰恰相反。它的设计假设你知道你在做什么。
  • 如果定义在不同的编译单元(不可用),你必须假设一切都必须工作。所以,没有办法知道foo 返回什么。明天程序员可能会来,有时会返回*this,有时会返回static A a; return a;
  • @R. Martinho Fernandes - 措辞不佳,同意。我想说的(很糟糕)是 C++ 采用了一种非常中级的语言方法来解决问题。这意味着除非您明确告诉它要做什么,否则它会变得盲目(在许多情况下,明显的例外是模板参数扩展)。结果是您必须明确定义动作/身份/等的流程,而不是知道它将隐式地正确解析/识别/等。基本上,它假定您无法跟踪头脑中的隐含事件链(因此,它假定您不知道自己在做什么)。
  • @R. Martinho Fernandes - 反过来看,你是完全正确的,它假设你知道你在做什么,因为你需要告诉它什么是正确的。
  • @UncleBens - 好点。这样就可以 100% 排除非模板解决方案。任何基于模板的?

标签: c++ templates inheritance c++11


【解决方案1】:

显而易见的解决方案是将其分成两行:

yarp.foo();
yarp.bar();

或者,或者,使用 static_cast 来取回对 B& 的引用,所以

static_cast<B&>(yarp.foo()).bar();

同意,这有点冗长,但是像这样在一行中将多个成员函数调用链接在一起是非常不寻常的 C++ 语法。它只是没有出现很多,所以语言不能很好地支持这个成语。我从未遇到过遇到此问题的情况。

如果您想设计一些可链接的功能,您可以使用其他更好的习惯用法。一个例子是 Boost 的 Range Adapters,它重载了 operator|。实现链接。

编辑:另一种选择是在 B& 中重载 foo():

class B: public A {
    public:
        B& foo() { A::foo(); return *this; }

        B& bar() {
            // do something mutant barish
            return *this;
        };
};

【讨论】:

  • 显而易见的解决方案不是我在这里挖掘的。是的,这很明显,但我正在寻找编译器来向前识别正在发生的事情。当一个库变得高度灵活和抽象时,第一个明显的解决方案就不再可能了。第二种情况是有问题的,有两个原因......大量的复制和粘贴代码和 gcc 似乎并没有优化它(我仍然不明白为什么,因为它只是一个重铸而不是真正的代码)这是不好的因为您在调用时创建了不必要的堆栈帧。
  • 如果您要让yarp.foo().bar() 模式工作,我使用重载的 B::foo() 进行的编辑将以最小的开销完全满足您的需求。如果您希望编译器能够自动确定 foo() 返回的 A& 仍然是 B& 并因此支持 bar(),这超出了 C++ 编译器的能力。不过,添加该功能很简单 - 只需在要支持它的类中添加一行重载,就完成了。
  • 再次,是的,但它仍然为调用/返回创建了一个不必要的堆栈框架,这在您优化速度时很糟糕。此外(同样重要的是),如果您任意重载模板化代理类中的每个运算符,那么继承链中的第三个或第四个子级会产生大量复制和粘贴的代码。
  • 该调用将被完全内联和优化,因此根本不会对性能造成任何影响。如果子类中的混乱是一个问题,您可以使用模板将代理类的函数定义移出子类的定义。
  • 嗯...好吧,也许我还没有反汇编测试二进制文件,但没有,在 gcc 中它似乎没有使用 -O3 进行优化。我会仔细检查也许这已经改变了。即使它已经改变,如果你阅读完整的帖子,你会发现这样的问题只是问题的一半(复杂代码标识的优雅是问题)。
【解决方案2】:

我认为没有自动类型检测,因为编译器甚至不知道哪些类将继承 A

在您的第二次试用中,C++ 禁止 using 模板专业化。所以这不会编译。

我认为您可以尝试的另一个技巧是将A 设为模板

template <typename FinalType>
class A {
    public:
        FinalType& foo() {
            // do something fooish
            return static_cast<FinalType&>(*this);
        };
};

class B: public A<B> {
    public:
        B& bar() {
            // do something barish
            return *this;
        };
};

【讨论】:

  • 只需将 A::foo 设为模板函数即可。
  • 所以模板应该自动扩展是你的意思吗?我已经尝试过(很多次),但它似乎不起作用。至少对于 gcc 4.5+。如果是这样,那就太好了。
  • 天哪。我意识到那里发生了什么。这与我对复杂模板所做的事情相同。好的,是的,它有效,但它有几个问题。首先,每个“B”类都必须声明 A。没什么大不了的,但仍然有点违反优雅条款。棘手的地方是 A 是 template (如您所说)并且 that 被另一个模板类继承。通过自动扩展。
  • class A { public: A&amp; foo(); }; 这是问题的症结所在。编译器无法猜测您返回的是*this
  • 允许编译器对标准库的实现使用保留名(其实我相信是必须的),以确保用户定义的代码——例如宏——不破坏标准图书馆或其他方式。 - 标准库的实现决不能被视为风格指南。
【解决方案3】:

Erm 你声明了一个没有方法 foo 的类 B 的实例 - 所以难怪会出现编译错误 - 你的意思是 yarp.bar().foo();

【讨论】:

  • 呃,没有。这里的目标是让 C++ 认识到因为在当前范围内 yarp 是 B 的一个实例,所以 foo() 返回 B&。我在帖子里没有说清楚吗? (编辑):或者更简单,通过模板/自动/等 B&B::foo() 将 C++ 放入工厂
  • yarp.foo() 返回一个 &A 为什么你会期望它不这样做。我有一种感觉,您正在尝试做其他事情,尽管它有一个更简单的解决方案 - 您是否有一个简单的现实世界示例来说明您正在尝试完成的事情
  • 想得更抽象一点。想象一下,这个场景经历了几个层次的继承和模板。我可以发布一个具有明确(虚拟)最终目标的更“完整”的具体示例,但我的问题实际上是关于编译器语言学的。摘要。有没有办法在不明确定义的情况下通过 A 转发 B 的身份?
猜你喜欢
  • 2017-05-21
  • 2014-09-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多