问题 关于std :: variant提出的接口中三个构造函数的问题


为什么构造函数(4)存在 std::variant 从 http://en.cppreference.com/w/cpp/utility/variant/variant?看起来这会导致代码中出现很多歧义,否则可以通过显式来避免这种歧义。例如,cppreference上的代码示例突出显示用户可能没有注意到的可能的歧义(第三行)

variant<string> v("abc"); // OK
variant<string, string> w("abc"); // ill-formed, can't select the alternative to convert to
variant<string, bool> w("abc"); // OK, but chooses bool

有些情况下绝对需要吗?

另一个问题是为什么需要从同一个cppreference页面构造函数(6)和(8)。 (5)和(7)不会达到(6)和(8)的目的吗?我可能误解了他们的用法..


对于读者来说,我在问题中提到的构造函数是

constexpr variant();              // (1)    (since C++17)

variant(const variant& other);    // (2)    (since C++17)

variant(variant&& other);         // (3)    (since C++17)

template< class T >               // (4)    (since C++17)
constexpr variant(T&& t);

template< class T, class... Args >
constexpr explicit variant(std::in_place_type_t<T>, Args&&... args); // (5) (since C++17)

template< class T, class U, class... Args >
constexpr explicit variant(std::in_place_type_t<T>,
                           std::initializer_list<U> il, Args&&... args); // (6) (since C++17)

template< std::size_t I, class... Args >
constexpr explicit variant(std::in_place_index_t<I>, Args&&... args) // (7) (since C++17)

template <size_t I, class U, class... Args>
constexpr explicit variant(std::in_place_index_t<I>,
                           std::initializer_list<U> il, Args&&... args); // (8) (since C++17)

878
2017-08-30 21:55


起源

嗯,你真的想用吗? in_place<T> 构造函数 一切 你用其中一个有界类型初始化变量的时间? - Brian
至于initializer_list构造函数,我很确定如果你想直接传递一个braced-init-list而不是构造一个initializer_list对象然后传入它,那么这些是必需的。 - Brian
@Brian个人来说,似乎对于这个界面。我认为这是构建变体的最佳方式。因为(4)很多次都可能含糊不清...... - Curious
关于initializer_list构造函数的@Brian,奇怪的是我从没想过会出现这个问题...... - Curious
我不同意这样的理由,因为(4)可能含糊不清,我们应该让每个人的生活更加艰难,以避免这种情况。 C ++不是那种语言。 - Brian


答案:


有些情况下绝对需要吗?

不,但事情并没有增加,因为它们“绝对需要”。他们被添加,因为他们是 有用。

并且可以从其中一个组件类型隐式转换对于a非常有用 variant。是的,它会在某些极端情况下产生歧义。但这种模糊性通常是由于类型设计中的缺陷(比如字符串文字更喜欢转换为 bool 超过用户定义的转换)。

如果有一个模糊的情况,那么你只需要明确它。喜欢用 "abc"s UDL文字而不是裸字符串文字(这是另一个原因)。但是没有理由  当你处理精心设计的类型时,每个人都要明确。

(5)和(7)不会达到(6)和(8)的目的吗?

不合理的方式。

在标准的每种情况下,当一个函数接受将传递给构造函数的可变参数时,它们将使用 构造函数语法 而不是 {} 该对象的语法。所以,如果你有这个:

using type = vector<int>;
variant<type> t(in_place<type>, 6);

你会接到一个电话 vector<int>(6)。请注意,这与...不同 vector<int>{6}。也就是说,除非实际传递初始化列表,否则不会获得初始化列表构造函数。

现在,您可以这样做:

variant<type> t(in_place<type>, initializer_list<int>{6});

但那过于冗长。相比之下:

variant<type> t(in_place<type>, {6});

这远不那么冗长。编译器可以推导出初始化列表的类型。如果您尝试推导出模板参数类型推导失败 braced-init-list 任意的 T

除其他方式外,模板扣除与扣除不同 auto 因为它没有推断出来 initializer_list来自 braced-init-list 表达式。例如

template <typename Type>
void func(const Type&);

不会推断 Type 是一个 std::initializer_list 进行以下调用

func({1, 2, 3, 4, 5});

有关这方面的更多信息,请参阅 通用引用和std :: initializer_list


10
2017-08-30 22:21



当你尝试推导出一个时,为什么演绎失败了 braced-init-list 任意的 T?我认为它应该默认为 initializer_list?我这样说是因为 auto 用一个 braced-init-list 默认为 initializer_list - Curious
@Curious这是一个单独的问题,应该单独询问。我也想知道答案。 - Brian
@Brian现在发帖。我一完成就会把你链接到它 - Curious
@Curious实际上我意识到这一定是以前曾经被问过的; stackoverflow.com/a/17622767/481267 - Brian