5

结构化绑定功能表示,如果tuple_size模板是完整类型,它会与元组类似的分解一起使用。当std::tuple_size给定类型在程序中的某一点是完整类型而在另一点不完整时会发生什么?

#include <iostream>
#include <tuple>

using std::cout;
using std::endl;

class Something {
public:
    template <std::size_t Index>
    auto get() {
        cout << "Using member get" << endl;
        return std::get<Index>(this->a);
    }

    std::tuple<int> a{1};
};

namespace {
    auto something = Something{};
}

void foo() {
    auto& [one] = something;
    std::get<0>(one)++;
    cout << std::get<0>(one) << endl;
}

namespace std {
template <>
class tuple_size<Something> : public std::integral_constant<std::size_t, 1> {};
template <>
class tuple_element<0, Something> {
public:
    using type = int;
};
}

int main() {
    foo();
    auto& [one] = something;
    cout << one << endl;
}

(此处转载https://wandbox.org/permlink/4xJUEpTAyUxrizyU

在上面的程序中,类型在程序Something中的一个点通过公共数据成员进行分解,并在另一个点像分解一样回退到元组。我们std::tuple_size是否在幕后使用隐含的“已完成”检查违反了 ODR?

4

1 回答 1

5

我看不出有任何理由相信有问题的程序格式不正确。简单地在代码中添加某些内容取决于类型的完整性,然后在稍后添加其他内容取决于类型已完成的同一类型的完整性,这并不违反标准。

如果我们有类似的东西,就会出现问题

inline Something something;  // external linkage
inline void foo() {
    auto& [one] = something;
}

在多个翻译单元中定义,其中一些翻译单元在定义时std::tuple_size<Something>已经完成foo,而在其他翻译单元中,它不是。这似乎绝对应该违反 ODR,因为实体one在不同的副本中接收不同的类型foo,但是,我实际上无法在标准中找到这样说的地方。将多个定义合并为一个的标准是:

  • D 的每个定义都应由相同的记号序列组成;和

  • 在 D 的每个定义中,根据 6.4 查找的相应名称应指在 D 的定义中定义的实体,或应指同一实体,在重载决议 (16.3) 和部分模板特化 (17.8) 匹配之后.3),但名称可以指代

    • 一个非易失性 const 对象,如果该对象具有内部链接或没有链接

      • 在 D 的所有定义中具有相同的文字类型,
      • 用常量表达式(8.20)初始化,
      • 在 D 的任何定义中都没有使用 odr,并且
      • 在 D 的所有定义中具有相同的值,

      或者

    • 使用常量表达式初始化的具有内部链接或没有链接的引用,使得该引用在 D 的所有定义中引用相同的实体;

  • 在 D 的每个定义中,对应的实体应具有相同的语言链接;和

  • 在 D 的每个定义中,所指的重载操作符、对转换函数、构造函数、操作符新函数和操作符删除函数的隐式调用应指同一函数,或指在 D 定义中定义的函数;和
  • 在 D 的每个定义中,(隐式或显式)函数调用使用的默认参数被视为其标记序列存在于 D 的定义中;也就是说,默认参数受本段所述要求的约束(并且,如果默认参数具有带有默认参数的子表达式,则此要求递归适用) 28 ;和
  • 如果 D 是一个具有隐式声明的构造函数 (15.1) 的类,就好像构造函数在使用 odr 的每个翻译单元中都隐式定义,并且每个翻译单元中的隐式定义应调用相同的构造函数D的子对象。

如果这里有一条规则使我的代码格式错误,我不知道它是哪一条。也许该标准需要修改,因为这不可能是允许的。

使程序格式错误的 NDR 的另一种方法涉及使用模板:

template <int unused>
void foo() {
    auto& [one] = something;
}
// define tuple_element and tuple_size
foo<42>(); // instantiate foo

这将与 [temp.res]/8.4 发生冲突,据此

该程序格式错误,不需要诊断,如果 ... [一个不依赖于模板参数的构造] 在 [模板的假设实例化紧随其定义] 的解释不同于模板的任何实际实例中的相应构造

于 2018-02-08T08:12:17.857 回答