为什么 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)};
}

live example

编译器输出



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_tuint64_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)};
}

live example

无需使用繁琐in_place_type。当然有用,但不是这种情况。

谢谢大家关注