【问题标题】:Why can we not access elements of a tuple by index?为什么我们不能通过索引访问元组的元素?
【发布时间】:2020-02-12 19:56:13
【问题描述】:
tuple <int, string, int> x=make_tuple(1, "anukul", 100);
cout << x[0];       //1
cout << get<0>(x);  //2

2 作品。 1 没有。

为什么会这样?

我从 Lounge C++ 了解到,这可能是因为编译器不知道该索引处存储的数据类型。 但这对我来说没有多大意义,因为编译器可以只查找该元组的声明并确定数据类型,或者在通过索引访问其他数据结构的元素时执行其他任何操作。

【问题讨论】:

  • 顺便说一句,恕我直言,正确的模式应该是x.0
  • @PaoloM 除了函数名不能以数字开头。
  • 重复?这是一个漫长的过程!
  • @UmNyobe 嗯,我不知道社区为什么要这样做?但是无法做到这一点的原因非常在其他问题的答案中有很好的记录。
  • @TartanLlama 我的意思是std::tuple 更像是一个未命名的结构而不是一个数组

标签: c++ stl


【解决方案1】:

因为 [] 是一个运算符(名为 operator[]),因此是一个成员函数,并且在运行时被调用。

虽然获取元组项是一种模板机制,但它必须在编译时解决。这意味着这只能通过 模板语法来完成。

为了更好地理解,一个元组可以存储不同的类型。模板函数可能会根据传递的索引返回不同的类型,因为这是在编译时解决的。 无论传递的参数的值是什么,operator[] 都必须返回一个唯一的类型。因此无法实现元组功能。

get&lt;0&gt;(x)get&lt;1&gt;(x) 是编译时生成的两个不同的函数,返回不同的类型。编译器实际上生成了两个函数,它们将被修改为类似

int get_tuple_int_string_int_0(x)

string get_tuple_int_string_int_1(x)

【讨论】:

    【解决方案2】:

    这里的其他答案解决了为什么这不可能实现的问题,但也值得提出它是否应该可能的问题。 (答案是否定的。)

    下标运算符[] 在语义上应该表示对集合元素的动态解析访问,例如数组或列表(任何实现)。访问模式通常意味着某些事情:周围的代码可能不知道元素的数量,正在访问的元素可能在运行时会有所不同,并且元素都是相同的可观察类型(因此,对于调用代码,可互换)。

    问题是,元组不是(那种)集合。它实际上是一个匿名的struct,它的元素根本不是可互换的插槽——从语义上讲,它们是常规字段。可能让你失望的是它们碰巧用数字标记,但这实际上只是一种匿名命名模式 - 类似于访问 x._0x._1 等元素。(事实上你可以计算字段名称 在编译时是由 C++ 的类型系统启用的一个巧合的好处,并且与元组是什么没有根本关系;元组和这个答案并不是真正特定于 C++。)

    所以它不支持operator[],原因与普通旧结构不支持operator[] 相同:在这种情况下,它没有语义上有效的用途。结构具有一组固定的不可互换或动态计算的字段,并且由于元组 一个结构,而不是一个集合,它遵循相同的规则。它的字段名称只是看起来不同。

    【讨论】:

    • 类型-理论上,元组和记录都是产品类型,唯一的区别是记录的组件被标记,而元组没有。所以,是的,元组与带有匿名字段的struct(这是 C++ 拼写“记录”的方式)几乎完全相同。
    【解决方案3】:

    可以支持,只需要编译时索引。由于不能将函数的参数设为constexpr,因此我们需要将索引包装在一个类型中并传递它。 (例如std::integral_constant&lt;std::size_t, N&gt;

    以下是支持operator[]std::tuple的扩展。

    template <typename... Ts>
    class tuple : public std::tuple<Ts...> {
      public:
      using std::tuple<Ts...>::tuple;
    
      template <std::size_t N>
      decltype(auto) operator[](std::integral_constant<std::size_t, N>) {
        return std::get<N>(*this);
      }
    };
    

    它会这样使用:

    tuple<int, std::string> x(42, "hello");
    std::cout << x[std::integral_constant<std::size_t, 0>{}] << std::endl;
    // prints: 42
    

    为了缓解std::integral_constant 的疯狂,我们可以使用变量模板:

    template <std::size_t N>
    std::integral_constant<std::size_t, N> ic;
    

    有了这个,我们可以说:

    std::cout << x[ic<1>] << std::endl;  // prints: hello
    

    这样就可以了。关于为什么目前不可用的一种猜测是因为std::integral_constant 和变量模板等功能在引入std::tuple 时可能还不存在。至于为什么有这些特性却不存在,我猜是因为还没有人提出来。

    【讨论】:

    • 当时绝对不存在变量模板,并且如您的示例所示,没有它们的语法非常粗略(尽管我们可以使用来自std::bind 的占位符来完成它,即x[_1],如果它们不是从一开始的索引而不是从零开始的索引!)。我完全同意我们没有它的原因是“为什么标准中没有这个?!”的通常答案。 ......没有人提出它。也许你应该:)
    • 为什么需要整数常量? conexpr 还不够吗?
    【解决方案4】:

    支持operator[] 不是很干净,因为您不能改变静态返回类型以匹配访问的元素。如果标准库包含boost::anyboost::variant 之类的东西,那就更有意义了。

    换一种说法,如果你写这样的东西:

    int n = atoi(argv[1]);
    int x = x[n];
    

    如果n 没有寻址到tupleint 成员,那该怎么办?为了支持检查,您需要为tuples 存储某种形式的运行时类型信息,这是可执行文件/内存中的额外开销。

    【讨论】:

    • 我们不能使用auto (C++ 14) 吗?
    • @AnukulSangwan: 否 - auto 必须能够在编译时计算出特定类型,因此它知道为对象保留多少内存、调用哪个析构函数等。跨度>
    【解决方案5】:

    支持下标运算符(即operator[])的容器,例如std::vectorstd::array,是同质值的集合.无论提供给下标运算符的索引是什么,要返回的值总是相同的类型。因此,这些容器可以定义一个成员函数,声明如下:

    T& operator[](int);
    

    其中T集合中每个元素的类型

    另一方面,std::tupe异构 值的集合。 std::tuple 的假设下标运算符的返回值需要随索引而变化。因此,它的返回类型取决于索引。

    在上面给出的operator[] 的声明中,索引作为函数参数提供,因此可以在运行时确定。但是,函数的返回类型需要在编译时确定,而不是在运行时确定。

    由于此类函数的返回类型取决于索引但必须在编译时确定,因此解决方案是定义一个函数 template 来接受索引作为 (非类型)模板参数。这样,索引作为编译时常量提供,并且返回类型能够随着索引而改变:

    template<std::size_t I, class... Types>
    typename std::tuple_element<I, tuple<Types...>>::type& get(tuple<Types...>&) noexcept;
    

    如您所见,std::get 的返回类型取决于索引,I

    std::tuple_element<I, tuple<Types...>>::type&
    

    【讨论】:

      【解决方案6】:

      因为元组没有运算符“括号”
      为什么会这样?您不能仅根据返回值解析模板。你不能写

      template<typename T>
      T tuple::operator [](size_t i) const ;
      

      这对于允许x[0] 之类的语句是绝对必要的

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-12-14
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多