我应该什么时候使用C ++ 14自动返回types演绎?

随着GCC 4.8.0的发布,我们有一个支持自动返回types演绎的编译器,它是C ++ 14的一部分。 用-std=c++1y ,我可以这样做:

 auto foo() { //deduced to be int return 5; } 

我的问题是:我应该什么时候使用这个function? 什么时候有必要,什么时候使代码更清洁?

情况1

我能想到的第一种情况是尽可能的。 每一个可以这样写的函数应该是。 这个问题是,它可能并不总是使代码更具可读性。

情景2

下一个场景是避免更复杂的返回types。 作为一个非常简单的例子:

 template<typename T, typename U> auto add(T t, U u) { //almost deduced as decltype(t + u): decltype(auto) would return t + u; } 

我不相信这将是一个真正的问题,虽然我认为在某些情况下显式依赖于参数的返回types可能会更清晰。

情景3

接下来,为了防止冗余:

 auto foo() { std::vector<std::map<std::pair<int, double>, int>> ret; //fill ret in with stuff return ret; } 

在C ++ 11中,有时我们可以return {5, 6, 7}; 而不是一个向量,但是这并不总是成功的,我们需要在函数头部和函数体中指定types。 这纯粹是多余的,自动返回types的扣除使我们免于冗余。

场景4

最后,它可以用来代替非常简单的function:

 auto position() { return pos_; } auto area() { return length_ * width_; } 

有时候,我们可能会看看这个函数,想知道确切的types,如果没有提供,我们必须去代码中的另一个点,比如声明pos_地方。

结论

有了这些场景,其中哪些实际上certificate了这个特性有助于使代码更清洁? 我忽略了在这里提到的场景呢? 在使用此function之前应该采取哪些预防措施,以防止以后再咬我? 有没有什么新的function带来的表是不可能没有它?

请注意,多个问题旨在帮助您find解决这个问题的观点。

C ++ 11提出了类似的问题:什么时候在lambdaexpression式中使用返回types的扣减,何时使用autovariables。

对C和C ++ 03中的问题的传统回答是“跨语句边界,我们使types显式化,在expression式中它们通常是隐含的,但是我们可以通过强制转换来明确它们”。 C ++ 11和C ++ 1y介绍了types推理工具,以便您可以在新的地方省去types。

对不起,但你不打算通过制定一般规则来解决这个问题。 您需要查看特定的代码,并自行决定它是否有助于可读性来指定遍布各处的types:您的代码更适合说“这个事物的types是X”还是更适合你的代码要说:“这个东西的types与理解这部分代码无关:编译器需要知道,我们可能可以解决,但是我们不需要在这里说出来”?

由于“可读性”没有客观的定义[*],而且读者也有所不同,所以作为一个代码的作者/编辑者,风格指南不能完全满足您的责任。 即使风格指南确实规定了规范,但不同的人会喜欢不同的规范,并且往往会发现任何不熟悉的东西都是“不太可读的”。 所以,一个特定的风格规则的可读性往往只能在其他风格规则的背景下进行判断。

所有的场景(甚至是第一个场景)都可以用于某个人的编码风格。 就我个人而言,我发现第二个是最引人注目的用例,但即使如此,我预计这将取决于您的文档工具。 看到logging的function模板的返回types是auto ,而将其logging为decltype(t+u)创build一个已发布的界面,你可以(希望)依靠它是没有什么帮助的。

偶尔有人试图做一些客观的测量。 从某种程度上来说,任何人只要想出了统计意义上的显着性和普遍适用的结果,他们就完全被工作程序员所忽略,而赞成作者对“可读性”的本能。

一般来说,函数返回types对logging函数有很大的帮助。 用户将知道什么是预期的。 但是,有一种情况我认为可以放弃返回types以避免冗余。 这里是一个例子:

 template<typename F, typename Tuple, int... I> auto apply_(F&& f, Tuple&& args, int_seq<I...>) -> decltype(std::forward<F>(f)(std::get<I>(std::forward<Tuple>(args))...)) { return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(args))...); } template<typename F, typename Tuple, typename Indices = make_int_seq<std::tuple_size<Tuple>::value>> auto apply(F&& f, Tuple&& args) -> decltype(apply_(std::forward<F>(f), std::forward<Tuple>(args), Indices())) { return apply_(std::forward<F>(f), std::forward<Tuple>(args), Indices()); } 

这个例子取自官方的委员会文件N3493 。 这个函数的作用是将std::tuple的元素转发给函数并返回结果。 int_seqmake_int_seq只是实现的一部分,可能只会让任何试图理解它的用户感到困惑。

正如你所看到的,返回types不过是返回expression式的decltype 。 此外, apply_不是为了被用户看到,我不确定在与apply类似或多或less的情况下logging其返回types的有用性。 我认为,在这种特殊情况下,删除返回types使得函数更具可读性。 请注意,这个返回types实际上已经被删除,并被提案中的decltype(auto)所替代,以添加到标准N3915中 (也请注意,我的原始答案早于本文):

 template <typename F, typename Tuple, size_t... I> decltype(auto) apply_impl(F&& f, Tuple&& t, index_sequence<I...>) { return forward<F>(f)(get<I>(forward<Tuple>(t))...); } template <typename F, typename Tuple> decltype(auto) apply(F&& f, Tuple&& t) { using Indices = make_index_sequence<tuple_size<decay_t<Tuple>>::value>; return apply_impl(forward<F>(f), forward<Tuple>(t), Indices{}); } 

但是,大多数情况下,最好保留这种返回types。 在上面描述的特定情况下,返回types是相当难以理解的,潜在的用户不会从中得知任何东西。 有例子的好文档将会更加有用。


另一件尚未提及的事情是: declype(t+u)允许使用expression式SFINAE , decltype(auto)不会(即使有build议改变这种行为)。 举例来说,一个foobar函数将会调用一个types的foo成员函数(如果存在的话),或者调用该types的bar成员函数(如果它存在),并假定一个类总是具有精确的foobar但是不能同时出现:

 struct X { void foo() const { std::cout << "foo\n"; } }; struct Y { void bar() const { std::cout << "bar\n"; } }; template<typename C> auto foobar(const C& c) -> decltype(c.foo()) { return c.foo(); } template<typename C> auto foobar(const C& c) -> decltype(c.bar()) { return c.bar(); } 

在一个X的实例上调用foobar会显示foo而在Y一个实例上调用foobar会显示bar 。 如果使用自动返回types推理(带有或不带有decltype(auto) ),则不会得到expression式SFINAE,并且对XY的实例调用foobar将触发编译时错误。

这是没有必要的。 至于什么时候你应该 – 你会得到很多不同的答案。 直到它实际上成为标准的接受部分,并且大部分主要编译器以相同的方式得到很好的支持,我才会说。

除此之外,它将成为一个宗教论证。 我个人认为,不要在实际的返回types中使代码更清晰,维护更容易(我可以看一个函数的签名,知道它返回什么,实际上是否需要读取代码)你认为它应该返回一种types,而编译器认为另一种会导致问题(就像我曾经使用过的每种脚本语言都发生过一样)。 我认为汽车是一个巨大的错误,它会导致更多的痛苦,而不是帮助。 其他人会说你应该一直使用它,因为它符合他们的编程哲学。 无论如何,这是超出了这个网站的范围。

这与函数的简单性没有任何关系(因为这个问题现在已经被删除了)。

返回types是固定的(不要使用auto ),或者以复杂的方式依赖于模板参数(在大多数情况下使用auto ,当有多个返回点时,使用decltype )。

考虑一个真实的生产环境:许多函数和unit testing都依赖于foo()的返回types。 现在假设返回types无论出于什么原因都需要改变。

如果返回types在所有地方都是auto ,并且在获取返回值时调用者到foo()和相关函数使用auto ,那么需要做的改变是最小的。 如果没有,这可能意味着几个小时极其繁琐且容易出错的工作。

作为一个真实世界的例子,我被要求改变一个模块从任何地方使用原始指针到智能指针。 修复unit testing比实际的代码更加痛苦。

虽然还有其他方法可以处理,但使用auto返回types似乎是一个很好的select。