为什么 unsigned 类型会使 std::variant<int64_t,uint64_t> 的构造变得模棱两可?
Why type unsigned makes std::variant<int64_t,uint64_t> ambiguous to construct?
我只想使用 unsigned(123)
和 123u
而不是 123ul
和 (unsigned long)(123)
。
#include <cstdint>
#include <variant>
using namespace std;
int main()
{
using var_t = variant<int64_t,uint64_t,double>;
var_t v1{1};
var_t v2{1ul};
var_t v3{1u}; //ERROR
// var_t v4{unsigned(1)}; //ERROR
var_t v5{uint64_t(1)};
}
编译器输出
g++ -std=c++20 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
main.cpp: In function 'int main()':
main.cpp:10:15: error: no matching function for call to 'std::variant<long int, long unsigned int, double>::variant(<brace-enclosed initializer list>)'
10 | var_t v3{1u}; //ERROR
| ^
In file included from main.cpp:2:
/usr/local/include/c++/10.2.0/variant:1402:2: note: candidate: 'template<long unsigned int _Np, class _Up, class ... _Args, class _Tp, class> constexpr std::variant<_Types>::variant(std::in_place_index_t<_Np>, std::initializer_list<_Up>, _Args&& ...) [with long unsigned int _Np = _Np; _Up = _Up; _Args = {_Args ...}; _Tp = _Tp; <template-parameter-2-5> = <template-parameter-1-5>; _Types = {long int, long unsigned int, double}]'
1402 | variant(in_place_index_t<_Np>, initializer_list<_Up> __il,
| ^~~~~~~
/usr/local/include/c++/10.2.0/variant:1402:2: note: template argument deduction/substitution failed:
main.cpp:10:15: note: mismatched types 'std::in_place_index_t<_Idx>' and 'unsigned int'
10 | var_t v3{1u}; //ERROR
| ^
In file included from main.cpp:2:
/usr/local/include/c++/10.2.0/variant:1391:2: note: candidate: 'template<long unsigned int _Np, class ... _Args, class _Tp, class> constexpr std::variant<_Types>::variant(std::in_place_index_t<_Np>, _Args&& ...) [with long unsigned int _Np = _Np; _Args = {_Args ...}; _Tp = _Tp; <template-parameter-2-4> = <template-parameter-1-4>; _Types = {long int, long unsigned int, double}]'
1391 | variant(in_place_index_t<_Np>, _Args&&... __args)
| ^~~~~~~
/usr/local/include/c++/10.2.0/variant:1391:2: note: template argument deduction/substitution failed:
main.cpp:10:15: note: mismatched types 'std::in_place_index_t<_Idx>' and 'unsigned int'
10 | var_t v3{1u}; //ERROR
| ^
In file included from main.cpp:2:
/usr/local/include/c++/10.2.0/variant:1381:2: note: candidate: 'template<class _Tp, class _Up, class ... _Args, class> constexpr std::variant<_Types>::variant(std::in_place_type_t<_Tp>, std::initializer_list<_Up>, _Args&& ...) [with _Tp = _Tp; _Up = _Up; _Args = {_Args ...}; <template-parameter-2-4> = <template-parameter-1-4>; _Types = {long int, long unsigned int, double}]'
1381 | variant(in_place_type_t<_Tp>, initializer_list<_Up> __il,
| ^~~~~~~
/usr/local/include/c++/10.2.0/variant:1381:2: note: template argument deduction/substitution failed:
main.cpp:10:15: note: mismatched types 'std::in_place_type_t<_Tp>' and 'unsigned int'
10 | var_t v3{1u}; //ERROR
| ^
In file included from main.cpp:2:
/usr/local/include/c++/10.2.0/variant:1371:2: note: candidate: 'template<class _Tp, class ... _Args, class> constexpr std::variant<_Types>::variant(std::in_place_type_t<_Tp>, _Args&& ...) [with _Tp = _Tp; _Args = {_Args ...}; <template-parameter-2-3> = <template-parameter-1-3>; _Types = {long int, long unsigned int, double}]'
1371 | variant(in_place_type_t<_Tp>, _Args&&... __args)
| ^~~~~~~
/usr/local/include/c++/10.2.0/variant:1371:2: note: template argument deduction/substitution failed:
main.cpp:10:15: note: mismatched types 'std::in_place_type_t<_Tp>' and 'unsigned int'
10 | var_t v3{1u}; //ERROR
| ^
In file included from main.cpp:2:
/usr/local/include/c++/10.2.0/variant:1361:2: note: candidate: 'template<class _Tp, class, class, class _Tj, class> constexpr std::variant<_Types>::variant(_Tp&&) [with _Tp = _Tp; <template-parameter-2-2> = <template-parameter-1-2>; <template-parameter-2-3> = <template-parameter-1-3>; _Tj = _Tj; <template-parameter-2-5> = <template-parameter-1-5>; _Types = {long int, long unsigned int, double}]'
1361 | variant(_Tp&& __t)
| ^~~~~~~
/usr/local/include/c++/10.2.0/variant:1361:2: note: template argument deduction/substitution failed:
/usr/local/include/c++/10.2.0/variant: In substitution of 'template<class ... _Types> template<class _Tp, class> using __accepted_type = std::variant<_Types>::__to_type<__accepted_index<_Tp> > [with _Tp = unsigned int&&; <template-parameter-2-2> = void; _Types = {long int, long unsigned int, double}]':
/usr/local/include/c++/10.2.0/variant:1357:9: required from here
/usr/local/include/c++/10.2.0/variant:1327:8: error: no type named 'type' in 'struct std::enable_if<false, void>'
1327 | using __accepted_type = __to_type<__accepted_index<_Tp>>;
| ^~~~~~~~~~~~~~~
/usr/local/include/c++/10.2.0/variant:1349:7: note: candidate: 'constexpr std::variant<_Types>::variant(std::variant<_Types>&&) [with _Types = {long int, long unsigned int, double}]'
1349 | variant(variant&&) = default;
| ^~~~~~~
/usr/local/include/c++/10.2.0/variant:1349:15: note: no known conversion for argument 1 from 'unsigned int' to 'std::variant<long int, long unsigned int, double>&&'
1349 | variant(variant&&) = default;
| ^~~~~~~~~
/usr/local/include/c++/10.2.0/variant:1348:7: note: candidate: 'constexpr std::variant<_Types>::variant(const std::variant<_Types>&) [with _Types = {long int, long unsigned int, double}]'
1348 | variant(const variant& __rhs) = default;
| ^~~~~~~
/usr/local/include/c++/10.2.0/variant:1348:30: note: no known conversion for argument 1 from 'unsigned int' to 'const std::variant<long int, long unsigned int, double>&'
1348 | variant(const variant& __rhs) = default;
| ~~~~~~~~~~~~~~~^~~~~
/usr/local/include/c++/10.2.0/variant:1347:7: note: candidate: 'constexpr std::variant<_Types>::variant() [with _Types = {long int, long unsigned int, double}]'
1347 | variant() = default;
| ^~~~~~~
/usr/local/include/c++/10.2.0/variant:1347:7: note: candidate expects 0 arguments, 1 provided
main.cpp:10:10: warning: unused variable 'v3' [-Wunused-variable]
10 | var_t v3{1u}; //ERROR
| ^~
The final rule we landed on 对于 variant
是我们只考虑转换是非缩小的替代方案,并且缩小确定考虑的是类型,而不是值。
除其他外:
- 整数到浮点数总是在缩小。
- 有符号到无符号总是在缩小。
- 如果有符号类型不能表示无符号类型的所有可能值,则无符号到有符号正在缩小。
所以:
- 对于
v1
,源类型是int
,唯一的非缩小选择是int64_t
,它被选中。
- 对于
v2
,来源类型是unsigned long
。如果 sizeof(int64_t) == sizeof(unsigned long)
,就像许多平台的情况一样,那么唯一的非缩小选择是 uint64_t
,并且选择了它。
- 对于
v3
,来源类型是unsigned int
。如果 int
是 32 位,那么 int64_t
和 uint64_t
都是可行的选择,并且没有一个比另一个更好。所以是模棱两可的。
对于这种事情,使用 in_place_type
来明确选择你想要的替代方案要清楚得多,而不是让 reader 在他们的头脑中进行重载决议。
答案将取决于平台,但在我的平台(64 位 linux + gcc C++)上,您的结果非常合理。请注意,在以下所有结果中,转换为 double
的排名低于积分促销,因此它不参与。
// Below works, as 1 is signed, and can only be promoted to int64_t.
variant<int64_t,uint64_t,double> v1{1};
// Below works, as 1ul is exactly unsigned long (64 bit for me), and is exact match
variant<int64_t,uint64_t,double> v2{1ul};
// Below doesn't work, as 1u is unsigned int, and can be equally promoted to int64_t and uint64_t - thus ambiguity
variant<int64_t,uint64_t,double> v3{1u}; //ERROR
// Below doesn't work, for exactly the same reason as one above.
variant<int64_t,uint64_t,double> v4{unsigned(1)}; //ERROR
}
我找到了一个优雅的解决方案,扩展 std::variant:
#include <cstdint>
#include <variant>
using namespace std;
struct var_t : variant<int64_t,uint64_t,double> {
using variant<int64_t,uint64_t,double>::variant;
constexpr var_t(unsigned u) : variant(uint64_t(u)) {}
};
int main()
{
var_t v1{1};
var_t v2{1ul};
var_t v3{1u}; // VALID
var_t v4{unsigned(1)}; // VALID
var_t v5{uint64_t(1)};
}
无需使用繁琐in_place_type
。当然有用,但不是这种情况。
谢谢大家关注
我只想使用 unsigned(123)
和 123u
而不是 123ul
和 (unsigned long)(123)
。
#include <cstdint>
#include <variant>
using namespace std;
int main()
{
using var_t = variant<int64_t,uint64_t,double>;
var_t v1{1};
var_t v2{1ul};
var_t v3{1u}; //ERROR
// var_t v4{unsigned(1)}; //ERROR
var_t v5{uint64_t(1)};
}
编译器输出
g++ -std=c++20 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
main.cpp: In function 'int main()':
main.cpp:10:15: error: no matching function for call to 'std::variant<long int, long unsigned int, double>::variant(<brace-enclosed initializer list>)'
10 | var_t v3{1u}; //ERROR
| ^
In file included from main.cpp:2:
/usr/local/include/c++/10.2.0/variant:1402:2: note: candidate: 'template<long unsigned int _Np, class _Up, class ... _Args, class _Tp, class> constexpr std::variant<_Types>::variant(std::in_place_index_t<_Np>, std::initializer_list<_Up>, _Args&& ...) [with long unsigned int _Np = _Np; _Up = _Up; _Args = {_Args ...}; _Tp = _Tp; <template-parameter-2-5> = <template-parameter-1-5>; _Types = {long int, long unsigned int, double}]'
1402 | variant(in_place_index_t<_Np>, initializer_list<_Up> __il,
| ^~~~~~~
/usr/local/include/c++/10.2.0/variant:1402:2: note: template argument deduction/substitution failed:
main.cpp:10:15: note: mismatched types 'std::in_place_index_t<_Idx>' and 'unsigned int'
10 | var_t v3{1u}; //ERROR
| ^
In file included from main.cpp:2:
/usr/local/include/c++/10.2.0/variant:1391:2: note: candidate: 'template<long unsigned int _Np, class ... _Args, class _Tp, class> constexpr std::variant<_Types>::variant(std::in_place_index_t<_Np>, _Args&& ...) [with long unsigned int _Np = _Np; _Args = {_Args ...}; _Tp = _Tp; <template-parameter-2-4> = <template-parameter-1-4>; _Types = {long int, long unsigned int, double}]'
1391 | variant(in_place_index_t<_Np>, _Args&&... __args)
| ^~~~~~~
/usr/local/include/c++/10.2.0/variant:1391:2: note: template argument deduction/substitution failed:
main.cpp:10:15: note: mismatched types 'std::in_place_index_t<_Idx>' and 'unsigned int'
10 | var_t v3{1u}; //ERROR
| ^
In file included from main.cpp:2:
/usr/local/include/c++/10.2.0/variant:1381:2: note: candidate: 'template<class _Tp, class _Up, class ... _Args, class> constexpr std::variant<_Types>::variant(std::in_place_type_t<_Tp>, std::initializer_list<_Up>, _Args&& ...) [with _Tp = _Tp; _Up = _Up; _Args = {_Args ...}; <template-parameter-2-4> = <template-parameter-1-4>; _Types = {long int, long unsigned int, double}]'
1381 | variant(in_place_type_t<_Tp>, initializer_list<_Up> __il,
| ^~~~~~~
/usr/local/include/c++/10.2.0/variant:1381:2: note: template argument deduction/substitution failed:
main.cpp:10:15: note: mismatched types 'std::in_place_type_t<_Tp>' and 'unsigned int'
10 | var_t v3{1u}; //ERROR
| ^
In file included from main.cpp:2:
/usr/local/include/c++/10.2.0/variant:1371:2: note: candidate: 'template<class _Tp, class ... _Args, class> constexpr std::variant<_Types>::variant(std::in_place_type_t<_Tp>, _Args&& ...) [with _Tp = _Tp; _Args = {_Args ...}; <template-parameter-2-3> = <template-parameter-1-3>; _Types = {long int, long unsigned int, double}]'
1371 | variant(in_place_type_t<_Tp>, _Args&&... __args)
| ^~~~~~~
/usr/local/include/c++/10.2.0/variant:1371:2: note: template argument deduction/substitution failed:
main.cpp:10:15: note: mismatched types 'std::in_place_type_t<_Tp>' and 'unsigned int'
10 | var_t v3{1u}; //ERROR
| ^
In file included from main.cpp:2:
/usr/local/include/c++/10.2.0/variant:1361:2: note: candidate: 'template<class _Tp, class, class, class _Tj, class> constexpr std::variant<_Types>::variant(_Tp&&) [with _Tp = _Tp; <template-parameter-2-2> = <template-parameter-1-2>; <template-parameter-2-3> = <template-parameter-1-3>; _Tj = _Tj; <template-parameter-2-5> = <template-parameter-1-5>; _Types = {long int, long unsigned int, double}]'
1361 | variant(_Tp&& __t)
| ^~~~~~~
/usr/local/include/c++/10.2.0/variant:1361:2: note: template argument deduction/substitution failed:
/usr/local/include/c++/10.2.0/variant: In substitution of 'template<class ... _Types> template<class _Tp, class> using __accepted_type = std::variant<_Types>::__to_type<__accepted_index<_Tp> > [with _Tp = unsigned int&&; <template-parameter-2-2> = void; _Types = {long int, long unsigned int, double}]':
/usr/local/include/c++/10.2.0/variant:1357:9: required from here
/usr/local/include/c++/10.2.0/variant:1327:8: error: no type named 'type' in 'struct std::enable_if<false, void>'
1327 | using __accepted_type = __to_type<__accepted_index<_Tp>>;
| ^~~~~~~~~~~~~~~
/usr/local/include/c++/10.2.0/variant:1349:7: note: candidate: 'constexpr std::variant<_Types>::variant(std::variant<_Types>&&) [with _Types = {long int, long unsigned int, double}]'
1349 | variant(variant&&) = default;
| ^~~~~~~
/usr/local/include/c++/10.2.0/variant:1349:15: note: no known conversion for argument 1 from 'unsigned int' to 'std::variant<long int, long unsigned int, double>&&'
1349 | variant(variant&&) = default;
| ^~~~~~~~~
/usr/local/include/c++/10.2.0/variant:1348:7: note: candidate: 'constexpr std::variant<_Types>::variant(const std::variant<_Types>&) [with _Types = {long int, long unsigned int, double}]'
1348 | variant(const variant& __rhs) = default;
| ^~~~~~~
/usr/local/include/c++/10.2.0/variant:1348:30: note: no known conversion for argument 1 from 'unsigned int' to 'const std::variant<long int, long unsigned int, double>&'
1348 | variant(const variant& __rhs) = default;
| ~~~~~~~~~~~~~~~^~~~~
/usr/local/include/c++/10.2.0/variant:1347:7: note: candidate: 'constexpr std::variant<_Types>::variant() [with _Types = {long int, long unsigned int, double}]'
1347 | variant() = default;
| ^~~~~~~
/usr/local/include/c++/10.2.0/variant:1347:7: note: candidate expects 0 arguments, 1 provided
main.cpp:10:10: warning: unused variable 'v3' [-Wunused-variable]
10 | var_t v3{1u}; //ERROR
| ^~
The final rule we landed on 对于 variant
是我们只考虑转换是非缩小的替代方案,并且缩小确定考虑的是类型,而不是值。
除其他外:
- 整数到浮点数总是在缩小。
- 有符号到无符号总是在缩小。
- 如果有符号类型不能表示无符号类型的所有可能值,则无符号到有符号正在缩小。
所以:
- 对于
v1
,源类型是int
,唯一的非缩小选择是int64_t
,它被选中。 - 对于
v2
,来源类型是unsigned long
。如果sizeof(int64_t) == sizeof(unsigned long)
,就像许多平台的情况一样,那么唯一的非缩小选择是uint64_t
,并且选择了它。 - 对于
v3
,来源类型是unsigned int
。如果int
是 32 位,那么int64_t
和uint64_t
都是可行的选择,并且没有一个比另一个更好。所以是模棱两可的。
对于这种事情,使用 in_place_type
来明确选择你想要的替代方案要清楚得多,而不是让 reader 在他们的头脑中进行重载决议。
答案将取决于平台,但在我的平台(64 位 linux + gcc C++)上,您的结果非常合理。请注意,在以下所有结果中,转换为 double
的排名低于积分促销,因此它不参与。
// Below works, as 1 is signed, and can only be promoted to int64_t.
variant<int64_t,uint64_t,double> v1{1};
// Below works, as 1ul is exactly unsigned long (64 bit for me), and is exact match
variant<int64_t,uint64_t,double> v2{1ul};
// Below doesn't work, as 1u is unsigned int, and can be equally promoted to int64_t and uint64_t - thus ambiguity
variant<int64_t,uint64_t,double> v3{1u}; //ERROR
// Below doesn't work, for exactly the same reason as one above.
variant<int64_t,uint64_t,double> v4{unsigned(1)}; //ERROR
}
我找到了一个优雅的解决方案,扩展 std::variant:
#include <cstdint>
#include <variant>
using namespace std;
struct var_t : variant<int64_t,uint64_t,double> {
using variant<int64_t,uint64_t,double>::variant;
constexpr var_t(unsigned u) : variant(uint64_t(u)) {}
};
int main()
{
var_t v1{1};
var_t v2{1ul};
var_t v3{1u}; // VALID
var_t v4{unsigned(1)}; // VALID
var_t v5{uint64_t(1)};
}
无需使用繁琐in_place_type
。当然有用,但不是这种情况。
谢谢大家关注