【问题标题】:C++ std::vector initializer_list overload ambiguity (g++/clang++)C++ std::vector initializer_list 重载歧义 (g++/clang++)
【发布时间】:2019-03-11 10:27:24
【问题描述】:

考虑以下代码:

#include <vector>

#define BROKEN

class Var {
public:
#ifdef BROKEN
    template <typename T>
    Var(T x) : value(x) {}
#else
    Var(int x) : value(x) {}
#endif

    int value;
};

class Class {
public:
    Class(std::vector<Var> arg) : v{arg} {}

    std::vector<Var> v;
};

无论BROKEN 是否被定义,Clang++ (7.0.1) 编译它都不会出错,但如果 BROKEN 被定义,g++ (8.2.1) 会引发错误:

main.cpp:9:20: error: cannot convert ‘std::vector<Var>’ to ‘int’ in initialization
  Var(T x) : value(x) {}
                    ^

据我所知,这里使用的统一初始化在两种情况下都应该选择std::vector(std::vector&amp;&amp;)构造函数;然而,显然g++{arg} 视为初始化列表,并尝试使用应用于向量的Var 来初始化v 的第一个元素,但这是行不通的。

如果没有定义BROKEN,g++ 显然足够聪明,可以意识到 initializer_list 重载不起作用。

哪个是正确的行为,还是标准允许的?

BROKEN defined gcc
BROKEN not defined gcc
BROKEN defined clang

【问题讨论】:

  • 我不是专家,但这里有一个有趣的数据点:如果您标记 ctor explicit,那么 g++ 会更喜欢它。
  • 另一个数据点:godbolt.org/z/gsc48J(您可以使用不同的优化级别)。显然,在调用std::vector 的复制构造函数之后,gcc 调用了Var::Var&lt;std::vector&lt;Var&gt; &gt;(std::vector&lt;Var&gt;)
  • 同理this post
  • @xskxzr 如果我理解正确,这意味着 clang 认为初始化列表(: value(x))中的类型错误意味着构造函数不可行,而 gcc 已经确定构造函数是在查看初始化列表之前可行。鉴于初始化列表通常可能位于不同的编译单元中,gcc 的行为似乎是合适的,这使得 clang 的不正确。这是真的吗?
  • @tomsmeding:Clang 不考虑template &lt;typename T&gt; Var(T x),即使你删除了函数体。 @xskxzr 链接的帖子在构造函数或初始化列表中不涉及任何可能的类型错误。

标签: c++ stdvector initializer-list uniform-initialization


【解决方案1】:

这里有两种方法可以解决这个问题:

class Var {
public:
#ifdef BROKEN
    template <typename T>
    Var(T x, typename std::enable_if<std::is_convertible<T, int>::value>::type* = nullptr) : value(x) {}
#else
    Var(int x) : value(x) {}
#endif

    int value;
};

Live demo.

或者:

class Class {
public:
    Class(std::vector<Var> arg) : v(arg) {}

    std::vector<Var> v;
};

Live demo.

现在显然在 gcc 的情况下尝试使用模板参数作为初始化列表。当使用大括号并定义了初始化列表时,初始化列表的优先级高于复制构造函数。

所以添加正确的enable_if 可以解决问题,因为它可以防止创建构造函数的初始化列表版本。在 C++20 中,概念会以更好的方式解决这个问题。

当使用括号代替大括号来初始化v时,初始化列表是不可取的。

我不确定谁是对的(IMO 叮当声,但这只是直觉)。也许更了解标准的人可以说出来。

【讨论】:

  • template &lt;typename T&gt; Var(T x); 如何成为 SFINAE 的案例?我不认为函数体(或初始化列表)是相关的,尤其是因为它可能位于不同的编译单元中。
猜你喜欢
  • 1970-01-01
  • 2016-02-21
  • 1970-01-01
  • 2015-04-30
  • 1970-01-01
  • 2017-08-15
  • 1970-01-01
  • 2012-07-09
  • 2016-04-26
相关资源
最近更新 更多