为什么不能使用兼容类型的std::tuple元素逐个构造std::tuple?

12

我无法从兼容类型的std::tuple初始化元素。为什么它不能像boost::tuple那样工作?

#include <tuple>
#include <boost/tuple/tuple.hpp>

template <typename T>
struct Foo
{
    // error: cannot convert 'std::tuple<int>' to 'int' in initialization
    template <typename U>
    Foo(U &&u) : val(std::forward<U>(u)) {}

    T val;
};

int main()
{
    boost::tuple<Foo<int>>{boost::tuple<int>{}};    // ok

    auto a = boost::tuple<int>{};
    boost::tuple<Foo<int>>{a};                      // ok

    std::tuple<Foo<int>>{std::tuple<int>{}};        // fails with rvalue

    auto b = std::tuple<int>{};
    std::tuple<Foo<int>>{b};                        // fails with lvalue
}
在 Coliru 上实时查看(GCC 或 Clang 和 libstdc++ 无法编译,但是Clang 和 libc++ 可以编译而不出现错误
std::tuple 没有进行元素级构造,它实例化了 Foo<int>::Foo<std::tuple<int>> 而不是 Foo<int>::Foo<int>。我认为 std::tuple::tuple 的第四种和第五种重载 就是为此而存在的。
template <class... UTypes>
tuple(const tuple<UTypes...>& other);

template <class... UTypes>
tuple(tuple<UTypes...>&& other);

注意:

只有当对于所有的 i,
std::is_constructible<Ti, const Ui&>::value 都为 true 时,才不参与重载决议。

std::is_constructible<Foo<int>, int>::value 的值为 true。从 GCC 的模板错误中可以看到,重载项 3:

template <class... UTypes>
explicit tuple(UTypes&&... args);

为什么被选择了呢?


好的,在Clang上,它不能使用-std=libstdc++选项工作,但可以使用-std=libc++选项工作。这一定是一个实现问题。 - LogicStuff
2
请在GCC的Bugzilla中提交一个错误报告。 - Marc Glisse
3
这个问题仍将为那些遇到相同问题的人提供有价值的信息。它会告诉他们问题出在标准库的实现,而不是他们的代码上。有许多常见的重复问题,这些问题解释了特定的实现错误(例如MinGW和stoi)。 - Revolver_Ocelot
1个回答

3

当传递一个 tuple& 时,重载(4)和(5)比(3)匹配度更低:它们是 const&&& 的重载,而(3)通过完美转发的魔法精确匹配。

(3)是有效的,因为您的 Foo(U&&) 构造函数过于贪婪。

Foo(U&&) 中添加 SFINAE 检查,以便在构建失败时无法匹配:

template <class U,
  std::enable_if_t<std::is_convertible<U,int>{},int>* =nullptr
>
Foo(U &&u) : val(std::forward<U>(u)) {}
然而,rvalue情况应该可以工作或者是不明确的。从你的实际例子的错误日志来看,我只看到一个与lvalue有关的错误。

1
对于匹配问题:const auto b 仍然会出现。您需要向 libstdc++(即 gcc 的 bugzilla)报告此问题。此外,您可能希望在 isocpp.org 论坛上询问是否可以添加一个接受非 const tuple 左值的构造函数,以便更好地匹配。 - Marc Glisse
1
@logic 感谢您修复错别字,但是 class= 的解决方案比 int*=nullptr 更糟糕。在多个重载的情况下,class= 会失败,而 int*= 不会。 - Yakk - Adam Nevraumont
@Yakk,rvalue情况也会产生错误,它在那里。 - LogicStuff
1
@LogicStuff void*在标准下不是合法的模板非类型参数类型。而int*则是合法的。我们总是将其设置为nullptr,它只是存在于SFINAE原因中,并且它比class=模式更好的一个SFINAE系统。int可以是任何非void类型,只是恰巧C++在这种特殊情况下禁止了void*。一些编译器无法强制执行该禁令,但我相信当代价仅为额外4个字符时,编写符合标准的代码是很有必要的。同样地,该int类型永远不会被使用 - Yakk - Adam Nevraumont
这个答案在我看来不够清晰。@Yakk,请问您能否编辑问题,标明分别是哪些项目为(1),(2)...(5)? - einpoklum
显示剩余3条评论

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