【问题标题】:std::variant converting constructor doesn't handle const volatile qualifiersstd::variant 转换构造函数不处理 const volatile 限定符
【发布时间】:2019-09-13 13:57:10
【问题描述】:

以下代码:

               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

coliru

所以std::variant 转换构造函数不考虑转换源类型的 const volatile 限定符。这是预期的行为吗?

关于从cppreference.com转换构造函数的信息

如果对来自 Types 的每个 T_i 存在虚函数 F(T_i) 的重载,则构造一个包含替代类型 T_j 的变体,该替代类型 T_j 将由表达式 F(std::forward&lt;T&gt;(t)) 的重载决策选择...

问题在于,在上述情况下,这种假想函数的重载集是模棱两可的:

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

coliru

cppreference.com 对此案只字未提。标准有规定吗?

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

【问题讨论】:

  • 请注意auto a = var_c 将丢弃constauto b = var_v 将丢弃volatile,这是您的问题的根源。 int 在调用构造函数时总是自动推导出来的。
  • @MarekR 自动推断在哪里?变体的构造函数使用转发引用,其中constvolatile 限定符保留在模板参数IIRC (live demo) 中。我相信问题在于那些通过价值传递的imaginary FUN functions

标签: c++ variant c++-standard-library


【解决方案1】:

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

函数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] [..] 生成参数类型列表后,任何修改参数类型的顶级cv-qualifiers在形成函数类型时都会被删除. [..]

【讨论】:

  • 我只是想知道为什么代码会编译。根据eel.is/c++draft/variant.ctor#12.sentence-2 的说法,如果有多个可行的重载,它不应该产生一些歧义错误吗?代码格式是否正确? (本说明中提到了一种相关案例:eel.is/c++draft/variant.ctor#17.note-1,尽管有相同的类型)。
  • @DanielLangr IMO,没有多个可行的重载。只有一个转换构造函数(调整后),可能是多重定义的(尽管这对模板来说是可以的)。不是真的确定这是否都是格式正确的。
  • @DanielLangr 这个注释很有趣。好吧,也许它的格式不正确:P
  • 不是构造函数的多个重载。我的意思是我链接的imaginary fun functions 的多个重载。
【解决方案2】:

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

您可以使用emplace 强制指定类型。

查看演示https://coliru.stacked-crooked.com/a/4dd054dc4fa9bb9a

【讨论】:

  • 这些警告有点令人担忧——它们是什么意思?
  • @LightnessRacesinOrbit 见hereemplace 返回构造值,gcc 警告丢弃对 volatile 返回值的引用不会对其执行读取。如果您确实从返回的引用中读取了 volatile: godbolt.org/z/F24Iea,警告就会消失
  • @MaxLanghof 明白了。
【解决方案3】:

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

[variant.ctor]/12 是这样说的:

T_j 是一个类型,确定如下:为每个替代类型T_i 构建一个虚构函数FUN(T_i)。重载决议为表达式 FUN(std::forward&lt;T&gt;(t)) 选择的重载 FUN(T_j) 定义了替代 T_j,它是后面包含的值的类型建设。

因此创建了四个函数:最初 FUN(int)、FUN(const int)、FUN(volatile int ) 和 FUN(const volatile int)。这些都是等价的签名,所以它们不能互相重载。这一段并没有真正指定如果无法实际构建重载集应该发生什么。但是,有一个注释强烈暗示了特定的解释:

[ 注意:
variant&lt;string, string&gt; v("abc");
格式不正确,因为两种替代类型都有一个同样可行的参数构造函数。 —尾注]

这个注释基本上是说重载解析不能区分stringstring。为了实现这一点,即使签名相同,也必须完成重载决议。这两个 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&lt;string, string&gt; 示例格式错误,因为重载决议选择的类型在备选列表中出现了两次。它说重载决议本身是模棱两可的(因为这两种类型有同样可行的构造函数)。这种区别很重要。如果此示例在重载解析阶段之后被拒绝,则可以声明您的代码格式正确,因为将从参数类型中删除顶级 cv 限定符,从而使所有四个重载有趣(int) 这样T_j = int。但是由于注释表明在重载解决期间 失败,这意味着您的示例不明确(因为 4 个签名是等效的)并且必须对此进行诊断。

【讨论】:

  • 这被trunk gcc和clang拒绝了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-09
  • 1970-01-01
  • 1970-01-01
  • 2022-07-20
  • 1970-01-01
  • 2019-03-22
相关资源
最近更新 更多