std::variant 转换构造函数不处理 const volatile 限定符
std::variant converting constructor doesn't handle const volatile qualifiers
代码如下:
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;
输出:
true
false
false
false
0
0
0
0
因此 std::variant
转换构造函数不考虑转换自类型的 const volatile 限定符。这是预期的行为吗?
关于从 cppreference.com
转换构造函数的信息
Constructs a variant holding the alternative type T_j that would be selected by overload resolution for the expression F(std::forward<T>(t))
if there was an overload of imaginary function F(T_i) for every T_i from Types...
问题是在上面的例子中,这种虚函数的重载集是不明确的:
void F( int) {}
void F(const int) {}
void F( volatile int) {}
void F(const volatile int) {}
cppreference.com 对这个案例只字不提。标准对此有规定吗?
我正在自己实现 std::variant
class。我转换构造函数的实现是基于。结果与上图相同(选择了第一个合适的备选方案,即使还有其他备选方案)。 libstdc++ 可能以相同的方式实现它,因为它也会选择第一个合适的替代方案。但我仍然想知道这是否是正确的行为。
是的,这就是按值传递时函数的工作方式。
函数void foo(int)
和函数void foo(const int)
和函数void foo(volatile int)
和函数void foo(const volatile int)
are all the same function.
通过扩展,您的变体的转换构造函数没有区别,并且没有有意义的方式来使用其替代品仅在其顶级 cv-qualifier 上不同的变体.
(好吧,您可以 emplace
使用显式模板参数,如 Marek 所示,但为什么呢?目的是什么?)
[dcl.fct/5]
[..] After producing the list of parameter types, any top-level cv-qualifiers modifying a parameter type are deleted when forming the function type. [..]
请注意,您正在创建值的副本。这意味着可以安全地丢弃 const
和 volatile
修饰符。这就是为什么模板总是推导 int
.
您可以使用 emplace 强制指定类型。
观看演示 https://coliru.stacked-crooked.com/a/4dd054dc4fa9bb9a
我对标准的理解是,由于歧义,代码应该是格式错误的。令我惊讶的是 libstdc++ 和 libc++ 似乎都允许它。
[variant.ctor]/12 是这样说的:
Let T
_j be a type that is determined as follows: build an imaginary function FUN(T
_i) for each alternative type T
_i. The overload FUN(T
_j) selected by overload resolution for the expression FUN(std::forward<T>(t))
defines the alternative T
_j which is the type of the contained value after construction.
所以创建了四个函数:最初是FUN(int
), FUN(const int
), FUN(volatile int
),以及 FUN(const volatile int
)。这些都是等效的签名,因此它们不能相互重载。本段并没有真正指定如果无法实际构建重载集应该发生什么。但是,有一个注释强烈暗示了特定的解释:
[ Note:
variant<string, string> v("abc");
is ill-formed, as both alternative types have an equally viable constructor for the argument. —end note]
这个注释基本上是说重载决议不能区分string
和string
。为了实现这一点,即使签名相同,也必须进行重载解析。两个 FUN(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
这里有两个相同的f
签名,都提交给重载决议,但没有一个比另一个更好。
回到标准的例子,似乎处方是应用重载决议程序,即使一些重载不能像普通函数声明一样相互重载。 (如果你愿意,可以想象它们都是从模板实例化的。)然后,如果重载决议不明确,std::variant
转换构造函数调用是错误的。
注释并没有说 variant<string, string>
示例格式错误,因为重载决策选择的类型在替代列表中出现了两次。它说重载决议本身是不明确的(因为这两种类型具有同样可行的构造函数)。这种区别很重要。如果此示例在重载决议阶段之后被拒绝,则可以提出您的代码格式正确的论点,因为顶级 cv 限定符将从参数类型中删除,使得所有四个重载 FUN(int
) 因此 T
_j = int
。但是由于该注释表明 在 重载解析期间失败,这意味着您的示例不明确(因为 4 个签名是等效的)并且必须对此进行诊断。
代码如下:
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;
输出:
true
false
false
false
0
0
0
0
因此 std::variant
转换构造函数不考虑转换自类型的 const volatile 限定符。这是预期的行为吗?
关于从 cppreference.com
转换构造函数的信息Constructs a variant holding the alternative type T_j that would be selected by overload resolution for the expression
F(std::forward<T>(t))
if there was an overload of imaginary function F(T_i) for every T_i from Types...
问题是在上面的例子中,这种虚函数的重载集是不明确的:
void F( int) {}
void F(const int) {}
void F( volatile int) {}
void F(const volatile int) {}
cppreference.com 对这个案例只字不提。标准对此有规定吗?
我正在自己实现 std::variant
class。我转换构造函数的实现是基于
是的,这就是按值传递时函数的工作方式。
函数void foo(int)
和函数void foo(const int)
和函数void foo(volatile int)
和函数void foo(const volatile int)
are all the same function.
通过扩展,您的变体的转换构造函数没有区别,并且没有有意义的方式来使用其替代品仅在其顶级 cv-qualifier 上不同的变体.
(好吧,您可以 emplace
使用显式模板参数,如 Marek 所示,但为什么呢?目的是什么?)
[dcl.fct/5]
[..] After producing the list of parameter types, any top-level cv-qualifiers modifying a parameter type are deleted when forming the function type. [..]
请注意,您正在创建值的副本。这意味着可以安全地丢弃 const
和 volatile
修饰符。这就是为什么模板总是推导 int
.
您可以使用 emplace 强制指定类型。
观看演示 https://coliru.stacked-crooked.com/a/4dd054dc4fa9bb9a
我对标准的理解是,由于歧义,代码应该是格式错误的。令我惊讶的是 libstdc++ 和 libc++ 似乎都允许它。
[variant.ctor]/12 是这样说的:
Let
T
_j be a type that is determined as follows: build an imaginary function FUN(T
_i) for each alternative typeT
_i. The overload FUN(T
_j) selected by overload resolution for the expression FUN(std::forward<T>(t))
defines the alternativeT
_j which is the type of the contained value after construction.
所以创建了四个函数:最初是FUN(int
), FUN(const int
), FUN(volatile int
),以及 FUN(const volatile int
)。这些都是等效的签名,因此它们不能相互重载。本段并没有真正指定如果无法实际构建重载集应该发生什么。但是,有一个注释强烈暗示了特定的解释:
[ Note:
variant<string, string> v("abc");
is ill-formed, as both alternative types have an equally viable constructor for the argument. —end note]
这个注释基本上是说重载决议不能区分string
和string
。为了实现这一点,即使签名相同,也必须进行重载解析。两个 FUN(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
这里有两个相同的f
签名,都提交给重载决议,但没有一个比另一个更好。
回到标准的例子,似乎处方是应用重载决议程序,即使一些重载不能像普通函数声明一样相互重载。 (如果你愿意,可以想象它们都是从模板实例化的。)然后,如果重载决议不明确,std::variant
转换构造函数调用是错误的。
注释并没有说 variant<string, string>
示例格式错误,因为重载决策选择的类型在替代列表中出现了两次。它说重载决议本身是不明确的(因为这两种类型具有同样可行的构造函数)。这种区别很重要。如果此示例在重载决议阶段之后被拒绝,则可以提出您的代码格式正确的论点,因为顶级 cv 限定符将从参数类型中删除,使得所有四个重载 FUN(int
) 因此 T
_j = int
。但是由于该注释表明 在 重载解析期间失败,这意味着您的示例不明确(因为 4 个签名是等效的)并且必须对此进行诊断。