【发布时间】:2019-10-10 19:52:45
【问题描述】:
用 C++17 标记,因为这是我引用的规范版本。
这是,IMO,标准的相关部分,第 23.15.7.6 节 [meta.trans.other]。
template <class... T> struct common_type;除非该特征是特化的(如下文注 B 中所述),否则 成员类型应按照以下注释 A 中的规定定义或省略。 如果省略,则不存在成员类型。中的每种类型 参数包 T 应该是完整的、cv void 或未知数组 绑定。
附注 A...
3.注意 A:对于应用于类型参数包 T 的 common_type trait,成员类型应按如下方式定义或不存在:
(3.1) — 如果
sizeof...(T)为零,则不应有成员类型。(3.2) — 如果
sizeof...(T)为1,则令T0 表示构成包T 的唯一类型。成员typedef-name type 应表示与common_type_t<T0, T0>;相同的类型(如果有),否则将没有成员输入。(3.3)——如果
sizeof...(T)为二,则构成T的第一类和第二类分别用T1和T2表示,令D1和D2分别表示与decay_t<T1>和decay_t<T2>相同的类型.(3.3.1) — 如果
is_same_v<T1, D1>是false或is_same_v<T2, D2>是false,让C 表示与common_type_t<D1, D2>相同的类型(如果有)。(3.3.2) — 否则,让 C 表示相同的类型,如果有的话,如
decay_t<decltype(false ? declval<D1>() : declval<D2>())>[注意:如果有专业化common_type<D1, D2>,这将不适用。 ——尾注]在任何一种情况下,成员 typedef-name type 都应表示与 C 相同的类型(如果有)。否则,将没有成员类型。
(3.4) — 如果
sizeof...(T)大于两个,则令 T1、T2 和 R 分别表示构成 T 的第一个、第二个和(一组)剩余类型。令 C 表示相同的类型,如果任何,如common_type_t<T1, T2>。如果存在这样的类型 C,则成员 typedef-name type 应表示与common_type_t<C, R...>相同的类型(如果有)。否则,将没有成员类型。
和注意 B...
4.注 B:尽管有 23.15.2 的规定,但根据 20.5.4.2.1,程序可以将
common_type<T1, T2>特化为 T1 和 T2 类型,使得is_same_v<T1, decay_t<T1>>和is_same_v<T2, decay_t<T2>>都为真。 [注意:当模板参数之间只需要显式转换时,需要这种特殊化。 ——尾注]这样的特化不需要有一个名为 type 的成员,但如果有,该成员应该是一个 typedef-name 用于可访问且明确的 cv-unqualified 非引用类型 C,其中每个类型 T1 和 T2 都是显式可转换。此外,
common_type_t<T1, T2>应表示与common_type_t<T2, T1>相同的类型(如果有的话)。违反本说明规则无需诊断。
基于此,我希望以下断言能够通过(他们确实如此)......
static_assert(not std::is_const_v<std::common_type_t<const volatile int, short>>);
static_assert(not std::is_volatile_v<std::common_type_t<const volatile int, short>>);
static_assert(std::is_same_v<std::common_type_t<int, short>, std::common_type_t<short, const volatile int>>);
static_assert(std::is_same_v<std::common_type_t<int &, short>, std::common_type_t<int, short>>);
同样,我希望这些也能通过......
using D1 = std::chrono::duration<int, std::ratio<2, 20>>;
using D2 = std::chrono::duration<short, std::ratio<7, 100>>;
static_assert(not std::is_const_v<std::common_type_t<const D1, D2>>);
static_assert(not std::is_volatile_v<std::common_type_t<volatile D1, D2>>);
static_assert(std::is_same_v<std::common_type_t<D1, D2>, std::common_type_t<const D1, D2>>);
static_assert(std::is_same_v<std::common_type_t<D1 &, D2>, std::common_type_t<D1, D2>>);
但是,我在调用 std::common_type 时遇到编译器错误,并在 std::chrono::duration 中添加了任何 cv 限定符或引用。
对我来说,标准似乎表明第二组断言应该成功,但它们在 clang 和 gcc 中都失败了。因此,我认为我误读了标准 - 或者两家供应商都有相同的错误,这不太可能(而且在打赌谁错时我总是和我一起去)。
也许语言“除非这个特征是专门化的(如下面的注释 B 中指定)”意味着注释 A 第 3.3 节的所有租户都没有申请专业化,但这意味着我们可以合法地得到非常奇怪的结果...在要求这个时就像一个编译错误...
using D1 = std::chrono::duration<int, std::ratio<2, 20>>;
using D2 = std::chrono::duration<short, std::ratio<7, 100>>;
static_assert(not std::is_const_v<std::common_type_t<const D1, D2>>);
std::common_type 的正常使用不需要在调用它之前删除 cv 限定符和引用 - 但似乎专业化可能要求调用者必须删除 cv 限定符和引用。
这似乎是错误的……某处。所以,我想弄清楚……是规范、供应商实现还是我的头脑有问题?
更新
编译器资源管理器中的 gcc/trunk 似乎成功编译了我认为应该编译的所有内容,尽管 gcc 9.2 以及所有 clang 版本都失败了。
【问题讨论】:
-
@NathanOliver 是的,但是 3.3.1 声明如果你传入
const版本,它应该衰减它(删除 const),type字段是common_type<D1,D2>,即专业的。 -
@Yakk-AdamNevraumont 是的。刚通过它回去。对我来说似乎是一个错误。
-
不是编译器问题,是标准库实现问题。 libstdc++ 曾经有一个显然已经修复的错误。 libc++ 有这个权利已经有一段时间了。 (tl;博士你是对的)。还有,你不必全力以赴用chrono,可以做点什么much simpler
-
您的最后一个示例使用 macOS 上的 clang/libc++ 和
-std=c++17或更高版本为我编译。 -
godbolt.org/z/z8HWlr -- 用 clang 在 Godbolt 上构建示例。刚改成libc++