C++ std::变量转换构造函数不';t handle常量volatile限定符

C++ std::变量转换构造函数不';t handle常量volatile限定符,c++,variant,c++-standard-library,C++,Variant,C++ Standard Library,代码如下: int i = 1; const int i_c = 2; volatile int i_v = 3; const volatile int i_cv = 4; typedef std::variant<int, const int, volatile int, const volatile int> TVariant; TVariant var (i ); TVariant var_c (

代码如下:

               int i    = 1;
const          int i_c  = 2;
      volatile int i_v  = 3;
const volatile int i_cv = 4;

typedef std::variant<int, const int, volatile int, const volatile int> TVariant;

TVariant var   (i   );
TVariant var_c (i_c );
TVariant var_v (i_v );
TVariant var_cv(i_cv);

std::cerr << std::boolalpha;

std::cerr << std::holds_alternative<               int>(var   ) << std::endl;
std::cerr << std::holds_alternative<const          int>(var_c ) << std::endl;
std::cerr << std::holds_alternative<      volatile int>(var_v ) << std::endl;
std::cerr << std::holds_alternative<const volatile int>(var_cv) << std::endl;

std::cerr << var   .index() << std::endl;
std::cerr << var_c .index() << std::endl;
std::cerr << var_v .index() << std::endl;
std::cerr << var_cv.index() << std::endl;

所以转换构造函数不考虑从类型转换的常量volatile限定符。这是预期行为吗

有关从转换构造函数的信息

构造一个变量,该变量包含替代类型T_j,如果类型中的每个T_i都有虚函数F(T_i)的重载,则该变量将通过重载解析为表达式
F(std::forward(T))
选择

问题在于,在上述情况下,此类虚函数的重载集不明确:

void F(               int) {}
void F(const          int) {}
void F(      volatile int) {}
void F(const volatile int) {}

cppreference.com对这起案件只字不提。标准是否规定了这一点


我正在实现自己的
std::variant
类。我的转换构造函数的实现基于。结果与上图相同(选择了第一个合适的备选方案,即使还有其他备选方案)。libstdc++可能以同样的方式实现它,因为它还选择了第一个合适的替代方案。但我仍然怀疑这是否是正确的行为。

是的,这就是传递值时函数的工作方式

函数
void foo(int)
和函数
void foo(const int)
和函数
void foo(volatile int)
和函数
void foo(const volatile int)

通过扩展,您的变体的转换构造函数没有什么区别,也没有任何有意义的方法来使用变体,因为其替代项仅在顶级cv限定符中有所不同

(好吧,正如Marek所示,你可以用一个显式的模板参数来放置,但是为什么?目的是什么?)

[…]生成参数类型列表后,在形成函数类型时,将删除修改参数类型的任何顶级cv限定符。[……]


请注意,您正在创建值的副本。这意味着可以安全地丢弃
const
volatile
修饰符。这就是为什么模板总是推断
int

您可以使用强制指定类型


参见演示

我对标准的理解是,由于模棱两可,代码应该是格式错误的。令我惊讶的是,libstdc++和libc++似乎都允许这样做

下面是[variant.ctor]/12所说的:

T
\u j是一种类型,其确定如下:为每个可选类型
T
\u i构建一个虚构的函数FUN(
T
\u i)。重载解析为表达式FUN
(std::forward(T))
选择的重载FUN(
T
\u j)定义了可选的
T
\u j,它是构造后包含的值的类型

因此创建了四个函数:最初的FUN(
int
)、FUN(
const-int
)、FUN(
volatile-int
)和FUN(
const-volatile-int
)。这些都是等效的签名,因此它们不能相互重载。本段并没有具体说明如果无法实际构建重载集,应该怎么做。然而,有一个注释强烈暗示了一种特殊的解释:

[注:
变体v(“abc”);

是格式错误的,因为两种可选类型的参数都有一个同样可行的构造函数。-结束注释]

这个注释基本上是说重载解析不能区分
string
string
。为了实现这一点,必须进行重载解析,即使签名相同。这两个有趣的(
string
)并没有折叠成一个函数

<>请注意,过载分辨率允许考虑由于模板导致的具有相同签名的重载。例如:

template <class T> struct Id1 { using type = T; };
template <class T> struct Id2 { using type = T; };
template <class T> void f(typename Id1<T>::type x);
template <class T> void f(typename Id2<T>::type x);
// ...
f<int>(0);  // ambiguous
模板结构Id1{using type=T;};
模板结构Id2{using type=T;};
模板void f(typename Id1::type x);
模板void f(typename Id2::type x);
// ...
f(0);//模棱两可的
这里有两个相同的签名
f
,它们都提交重载解析,但两者都不比另一个好

回到标准的例子,似乎处方是应用重载解析过程,即使一些重载不能作为普通函数声明彼此重载。(如果您愿意,假设它们都是从模板实例化的。)然后,如果重载解析不明确,则转换构造函数调用的
std::variant
格式不正确


注释并没有说
变体
示例格式不正确,因为重载解析选择的类型在备选方案列表中出现两次。它说重载解析本身是不明确的(因为这两种类型都有同样可行的构造函数)。这一区别很重要。如果此示例在重载解析阶段后被拒绝,则可能会有一个参数表明您的代码格式正确,因为顶级cv限定符将从参数类型中删除,从而使所有四个重载都变得有趣(
int
),从而
T
\u j=
int
)。但由于注释表明过载解决过程中出现故障,这意味着您的示例不明确(因为4个签名是等效的),必须对此进行诊断。

注意
auto a=var_c
将丢弃
const
,而
auto b=var_v
将丢弃
volatile
,这是您问题的根源<调用构造函数时,code>int
总是自动推断。@MarekR自动推断在哪里?Variant的构造函数使用转发引用,其中
const
volatile
限定符保存在模板参数IIRC()中。我相信问题出在那些忽略价值的人身上。这些警告有点令人担忧——它们是什么意思?@LightnessRacesinOrbit-See<代码>放置
返回co
template <class T> struct Id1 { using type = T; };
template <class T> struct Id2 { using type = T; };
template <class T> void f(typename Id1<T>::type x);
template <class T> void f(typename Id2<T>::type x);
// ...
f<int>(0);  // ambiguous