std::pair<auto, auto> 返回类型

17

我在玩弄std::pair中的auto。在下面的代码中,函数f应该返回一个取决于模板参数的类型的std::pair

一个工作示例:

示例1

template <unsigned S>
auto f()
{
    if constexpr (S == 1)
        return std::pair{1, 2}; // pair of ints
    else if constexpr (S == 2)
        return std::pair{1.0, 2.0}; // pair of doubles
    else
        return std::pair{0.0f, 0.0f}; // pair of floats
}

这可以在gcc 9.2、gcc 10.0、clang 9.0和clang 10.0上工作。

接下来,出于清晰的原因,我想明确地将返回类型写为std::pair

示例2

template <unsigned S>
std::pair<auto, auto> f()
{
    if constexpr (S == 1)
        return {1, 2};
    /* ... */
}

使用gcc 9.2/10.0和clang 9.0/10.0都无法编译此代码。

gcc 9.2

error: invalid use of 'auto'
error: template argument 1 is invalid // first argument (auto) of std::pair
error: template argument 2 is invalid // second argument (auto) of std::pair
error: cannot convert '<brace-enclosed initializer list>' to 'int' in return

从最后一个错误信息来看,gcc 9.2似乎认为std::pair<auto, auto>是一个int。这可以如何解释?

gcc 10.0

error: returning initializer list

这个错误是可以理解的,但是我期望std::pair的构造函数被调用,或者我是否遗漏了什么?

clang 9.0和10.0

'auto' not allowed in template argument
excess elements in scalar initializer
no matching function for call to 'f'

好的,clang不喜欢这些内容。从第二条错误信息来看,clang似乎也认为返回类型是int

最后,为了修复使用gcc 10.0编译时获取的错误,我决定明确返回一个std::pair

示例3

template <unsigned S>
std::pair<auto, auto> f()
{
    if constexpr (S == 1)
        return std::pair{1, 2};
    /* ... */
}

clang 9.0和10.0

与之前相同,但多了一个:

no viable conversion from returned value of type 'std::pair<int, int>' to function return type 'int'

clang仍然认为我们正在返回一个int?

gcc 9.2

与之前相同。

gcc 10.0

可以了!

我猜还有一些功能需要实现,或者在上述某种情况中,是否存在一个编译器是正确的,而另一个则错误?在我看来,样例2应该能够工作。或者不应该?

1个回答

23
语法:
std::pair<auto, auto> f() { return std::pair(1, 2); }
~~~~~~~~~~~~~~~~~~~~~

曾经是概念 TS 的一部分,但未被纳入 C++20 中的概念提案。因此,在 C++20 中,唯一的占位符类型是 auto(以及类似于 auto** 的变体)、decltype(auto) 和受限制的占位符(Concept auto 及其变体)。这种嵌套的占位符类型非常有用,但不是 C++20 的一部分,因此该函数声明是不合法的。

现在,gcc 允许它,因为 gcc 实现了概念 TS,我想他们决定保留这个功能。clang 从未实现过该 TS,因此不支持。

无论如何,下面的内容:

std::pair<auto, auto> f() { return {1, 2}; }

始终会形成不规范的形式。这个语法的意思是我们推断返回类型,然后要求它匹配某些类型 TUpair<T, U>。我们基本上正在尝试调用发明的函数:

template <typename T, typename U>
void __f(std::pair<T, U>);

__f({1, 2}); // this must succeed

但是你不能从 {1, 2} 推导出类型 - 大括号初始化列表没有类型。也许这是应该探讨的问题(至少在这种简单情况下很容易理解),但它从来没有被允许。因此,无论如何都是正确的拒绝它。
最后:

gcc 9.2似乎认为 std::pair<auto, auto> 是一个 int。这是怎么回事?

由于我们具有隐式int的C语言传统,当gcc无法识别或理解类型时,它会将int作为错误消息中的占位符。这非常令人困惑,因为显然是gcc提出了int,而不是源代码。但就是这样。

“braced-init-list doesn't have a type argument”对我来说不太清楚。std::pair<int, int> f() { return {1,2}; } 是可以工作的,而{1,2}没有类型(据我理解,它调用了std::pair<int,int>的构造函数)。也许使用<auto, auto>,编译器无法推断初始化列表{1, 2}中1和2的类型? - mfnx
@mfnx 不是没有类型的“参数”,而是根本没有类型。花括号初始化列表只能在某些情况下使用,比如初始化已知类型。但它们不能用于推导,因为它们没有类型。除了auto x = {1, 2};可以工作,但前提是所有类型都相同。 - Barry
我的意思是:对我来说那个参数不清楚(在单词“argument”之前应该关闭“”)。 - mfnx
2
大多数编译器在遇到第一个错误时不会停止,而是尝试从中恢复,以便报告其他错误。这通常意味着假设所有无法解析的内容都是“int”。这并不是说“int”是错误消息中的占位符;编译器真的认为它是“int”。(为了更清楚地说明这一点,gcc可能应该在某个时候说“假定int”)。 - Raymond Chen
2
请注意,扩展的另一个可能途径是允许类模板参数推导用于返回类型,因为“std::pair __f{1,2};”可以工作。 - Davis Herring
2
@DavisHerring 我并不希望 std::optional f() { return 4; } 能够工作。 - Barry

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