Mar*_*k R 19 c++ std language-lawyer c++17 c++20
关于这个问题的讨论是在这个非常简单的问题的答案下开始的。
\n这个简单的代码具有意外的构造函数重载解析std::basic_string:
#include <string>\n\nint main() {\n std::string s{"some string to test", 2, 3};\n return 0;\n}\nRun Code Online (Sandbox Code Playgroud)\n现在有人期望这会调用这个构造函数:
\nstd::basic_string<CharT,Traits,Allocator>::basic_string - cppreference.com
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
template<class T> basic_string( const T& t, size_type pos, size_type n, const Allocator& alloc = Allocator() ); (自 C++17 起) (11) template< class T > constexpr basic_string( const T& t, size_type pos, size_type const Allocator& alloc = Allocator() ); (自 C++20 起) (11)
基本原理基于此 C++ 标准的部分:
\n\n\n\n\n
template<class T> constexpr basic_string(const T& t, size_type pos, size_type n, const Allocator& a = Allocator());5 约束: is_\xc2\xadconvertible_\xc2\xadv<const T&, basic_\xc2\xadstring_\xc2\xadview<charT, Traits>> 为 true 。
\n6 效果: 创建一个变量 sv,就像 basic_\xc2\xadstring_\xc2\xadview<charT, Traits> sv = t; 然后其行为与: basic_string(sv.substr(pos, n), a); 相同
\n
现在,当cppinsights处理此代码时,结果与预期不同:
\n#include <string>\n\nint main()\n{\n std::string s = std::basic_string<char>{std::basic_string<char>("some string to test", std::allocator<char>()), 2, 3};\n return 0;\n}\nRun Code Online (Sandbox Code Playgroud)\n将此代码折叠为相应的类型定义后,std::string它变为:
#include <string>\n\nint main()\n{\n std::string s = std::string{std::string("some string to test"), 2, 3};\n return 0;\n}\nRun Code Online (Sandbox Code Playgroud)\nstd::string因此,执行字符串文字到的转换,然后使用形式(3)中的构造函数。因此执行了不希望的额外分配。
我确信这不是工具的错误,我做了其他实验来证实这一点。这是一个Godbolt 示例。组装显然遵循这种模式。
\n为了找到这个问题的解释,我在这段代码中引入了一个错误,因此错误报告会打印可能的重载解决方案。这是错误报告中最有趣的部分:
\n/opt/compiler-explorer/gcc-trunk-20220104/include/c++/12.0.0/bits/basic_string.h:771:9: note: candidate: \'template<class _Tp, class> std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::basic_string(const _Tp&, size_type, size_type, const _Alloc&) [with <template-parameter-2-2> = _Tp; _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]\'\n 771 | basic_string(const _Tp& __t, size_type __pos, size_type __n,\n | ^~~~~~~~~~~~\n/opt/compiler-explorer/gcc-trunk-20220104/include/c++/12.0.0/bits/basic_string.h:771:9: note: template argument deduction/substitution failed:\nIn file included from /opt/compiler-explorer/gcc-trunk-20220104/include/c++/12.0.0/bits/char_traits.h:42,\n from /opt/compiler-explorer/gcc-trunk-20220104/include/c++/12.0.0/string:40,\n from <source>:1:\n/opt/compiler-explorer/gcc-trunk-20220104/include/c++/12.0.0/type_traits: In substitution of \'template<bool _Cond, class _Tp> using enable_if_t = typename std::enable_if::type [with bool _Cond = false; _Tp = void]\':\n/opt/compiler-explorer/gcc-trunk-20220104/include/c++/12.0.0/bits/basic_string.h:156:8: required by substitution of \'template<class _CharT, class _Traits, class _Alloc> template<class _Tp, class _Res> using _If_sv = std::enable_if_t<std::__and_<std::is_convertible<const _Tp&, std::basic_string_view<_CharT, _Traits> >, std::__not_<std::is_convertible<const _Tp*, const std::__cxx11::basic_string<_CharT, _Traits, _Alloc>*> >, std::__not_<std::is_convertible<const _Tp&, const _CharT*> > >::value, _Res> [with _Tp = char [20]; _Res = void; _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]\'\n/opt/compiler-explorer/gcc-trunk-20220104/include/c++/12.0.0/bits/basic_string.h:769:30: required from here\n/opt/compiler-explorer/gcc-trunk-20220104/include/c++/12.0.0/type_traits:2614:11: error: no type named \'type\' in \'struct std::enable_if<false, void>\'\n 2614 | using enable_if_t = typename enable_if<_Cond, _Tp>::type;\n | ^~~~~~~~~~~\nRun Code Online (Sandbox Code Playgroud)\n因此重载(11)已被 SFINAE 拒绝。
\n相关标准库代码如下所示:\n std::basic_string 构造函数的重载(11) :
\n/opt/compiler-explorer/gcc-trunk-20220104/include/c++/12.0.0/bits/basic_string.h:771:9: note: candidate: \'template<class _Tp, class> std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::basic_string(const _Tp&, size_type, size_type, const _Alloc&) [with <template-parameter-2-2> = _Tp; _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]\'\n 771 | basic_string(const _Tp& __t, size_type __pos, size_type __n,\n | ^~~~~~~~~~~~\n/opt/compiler-explorer/gcc-trunk-20220104/include/c++/12.0.0/bits/basic_string.h:771:9: note: template argument deduction/substitution failed:\nIn file included from /opt/compiler-explorer/gcc-trunk-20220104/include/c++/12.0.0/bits/char_traits.h:42,\n from /opt/compiler-explorer/gcc-trunk-20220104/include/c++/12.0.0/string:40,\n from <source>:1:\n/opt/compiler-explorer/gcc-trunk-20220104/include/c++/12.0.0/type_traits: In substitution of \'template<bool _Cond, class _Tp> using enable_if_t = typename std::enable_if::type [with bool _Cond = false; _Tp = void]\':\n/opt/compiler-explorer/gcc-trunk-20220104/include/c++/12.0.0/bits/basic_string.h:156:8: required by substitution of \'template<class _CharT, class _Traits, class _Alloc> template<class _Tp, class _Res> using _If_sv = std::enable_if_t<std::__and_<std::is_convertible<const _Tp&, std::basic_string_view<_CharT, _Traits> >, std::__not_<std::is_convertible<const _Tp*, const std::__cxx11::basic_string<_CharT, _Traits, _Alloc>*> >, std::__not_<std::is_convertible<const _Tp&, const _CharT*> > >::value, _Res> [with _Tp = char [20]; _Res = void; _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]\'\n/opt/compiler-explorer/gcc-trunk-20220104/include/c++/12.0.0/bits/basic_string.h:769:30: required from here\n/opt/compiler-explorer/gcc-trunk-20220104/include/c++/12.0.0/type_traits:2614:11: error: no type named \'type\' in \'struct std::enable_if<false, void>\'\n 2614 | using enable_if_t = typename enable_if<_Cond, _Tp>::type;\n | ^~~~~~~~~~~\nRun Code Online (Sandbox Code Playgroud)\n这是_If_sv的实现:
\n template<typename _Tp, typename = _If_sv<_Tp, void>>\n basic_string(const _Tp& __t, size_type __pos, size_type __n,\n const _Alloc& __a = _Alloc())\n : basic_string(_S_to_string_view(__t).substr(__pos, __n), __a) { }\nRun Code Online (Sandbox Code Playgroud)\n所以这个条件的最后一部分:__not_<is_convertible<const _Tp&, const _CharT*>>>::value是一个问题,因为显然 char 数组(字符串文字)可以转换为指向字符的指针。
为什么执行起来是_If_sv这样的?标准中没有提到的这个额外条件的理由是什么?
如果我正确理解重载解析,则此额外条件已过时,因为当第一个参数为std::string重载时(3)优先于重载(11)作为非模板函数的精确匹配。另一方面,如果参数不是并且std::string可以转换为,std::string_view那么模板重载(11)应该获胜,并且可以避免额外的分配。
这是标准库实现中的错误还是我错过了什么?
\n为什么需要这些额外的 SFINAE 条件?
\n有没有一种好的方法来编写检测此问题的测试?\n一些方法可以验证是否已选择正确的重载而不修改测试代码(在本例中std::basic_string)?
Jon*_*ely 11
在 C++14 中,您的代码始终创建一个新的std::string临时对象,没有构造函数采用const char*或std::string_view可以使用。
在 C++17 中,您的代码应该创建一个string_view并避免临时的。GCC 的实现受到过度约束,因此仍然像 C++14 一样运行,从而创建了额外的临时文件。
为什么实现
_If_sv看起来像这样?标准中未提及的额外条件的理由是什么?
它具有三个条件,其中两个在标准中。第三个也有原因。
该帮助器实现了除您所询问的一个构造函数之外的每个接受 a 的函数_If_sv所需的约束。该约束的约束稍弱,允许将指针传递给它并转换为字符串视图。由于 libstdc++对该构造函数使用相同的帮助程序,因此它无法按标准要求工作。我现在已经解决了这个问题(PR 103919)。basic_stringbasic_string_view const CharT*_If_sv
在以下三个条件中_If_sv:
string_view标准要求“可转换为一”。const CharT*,所有函数的标准都要求“不可转换为”。basic_string”是为了修复我发现的由标准指定的约束引起的回归。这个问题如下所述,供那些不喜欢点击链接离开的人使用。如果我正确理解重载解析,这个额外的条件就已经过时了,
没有。
因为当第一个参数是
std::string重载时 (3) 优先于重载 (11) 作为非模板函数的精确匹配。
是的,如果它是 astd::string那么无论如何都会选择另一个构造函数。“不可转换为std::string”约束在这里是多余的,因为无论如何都会选择另一个构造函数。它无害,但多余。但如果论证不是,而是源自std::string某物怎么办?考虑:std::string
class tstring : public std::string
{
public:
tstring() : std::string() {}
tstring(tstring&& s) : std::string(std::move(s)) {}
};
Run Code Online (Sandbox Code Playgroud)
在 C++14 中,这basic_string(basic_string&&)按预期使用移动构造函数。在 C++17 中它使用basic_string(const T&, const Alloc& = Alloc()),因为tstring&&参数可转换为string_view. 这意味着我们复制s而不是按预期移动它。
T不可转换的附加约束从此处考虑中std::string删除了新的string_view重载,因此再次使用移动构造函数。因此,对于无论如何都不会选择构造函数的情况来说,额外的约束是无害的,但对于将选择但不应该选择新构造函数的情况很有用。basic_string(const T&, const Alloc&)
| 归档时间: |
|
| 查看次数: |
591 次 |
| 最近记录: |