为何C++17中std::pair异构“移动”构造函数的显式性发生变化?
先看问题中的代码示例:
#include <memory> #include <utility> using X = std::unique_ptr<int>; using A = std::pair<int, const X>; using B = std::pair<int, X>; static_assert(std::is_constructible<A, B>::value, "(1)"); // 合法 static_assert(std::is_convertible<B, A>::value, "(2)"); // C++14及以前被GCC和Clang拒绝 static_assert(std::is_constructible<const X, X>::value, "(3)"); // 合法 static_assert(std::is_convertible<X, const X>::value, "(4)"); // 合法
核心现象
在C14及更早版本中,libstdc和libc的实现里,std::pair<int, const std::unique_ptr<int>>从std::pair<int, std::unique_ptr<int>>右值构造的构造函数是explicit的,导致std::is_convertible<B, A>::value为false;而C17起该构造函数变为隐式,MSVC的标准库实现则始终将其设为隐式。
差异来源与C++14中explicit的原因
- C++14标准的规则歧义
在C++14及之前的标准中,std::pair的模板构造函数(对应cppreference的构造函数(6))的explicit属性定义存在歧义:标准描述中,构造函数是否为explicit取决于成员类型的转换特性,但并未明确区分“从右值构造const成员”这类场景的处理逻辑。
libstdc和libc的实现者将此规则解读为:当构造pair<T1, T2>时,若T2是const U2,即使U2可以隐式转换为const U2,pair的模板构造函数仍需标记为explicit——这是对标准规则的保守解读,导致了代码中(2)断言在C++14及以前被拒绝。
C++17的标准修正
C17标准明确了std::pair模板构造函数的explicit判定规则:仅当U1无法隐式转换为T1,或U2无法隐式转换为T2时,构造函数才会被标记为explicit。由于std::unique_ptr<int>可以隐式转换为const std::unique_ptr<int>,因此pair<B>到pair<A>的构造函数在C17及以后变为非explicit,is_convertible<B, A>::value也变为true。MSVC的实现差异
MSVC的标准库实现没有遵循libstdc++/libc对C14规则的保守解读,而是始终按照“成员可隐式转换则构造函数非explicit”的逻辑实现,因此在所有版本中该构造函数都是隐式的。
内容的提问来源于stack exchange,提问作者HolyBlackCat

