【问题标题】:Why 'is_convertible' here in <utility> std::pair (STL)?为什么在 <utility> std::pair (STL) 中出现“is_convertible”?
【发布时间】:2018-02-26 14:09:23
【问题描述】:
    template<class _Other1,
    class _Other2,
    class = enable_if_t<is_constructible<_Ty1, _Other1>::value
                    && is_constructible<_Ty2, _Other2>::value>,
    enable_if_t<is_convertible<_Other1, _Ty1>::value
            && is_convertible<_Other2, _Ty2>::value, int> = 0>
    constexpr pair(pair<_Other1, _Other2>&& _Right)
        _NOEXCEPT_OP((is_nothrow_constructible<_Ty1, _Other1>::value
            && is_nothrow_constructible<_Ty2, _Other2>::value))
    : first(_STD forward<_Other1>(_Right.first)),
        second(_STD forward<_Other2>(_Right.second))
    {   // construct from moved compatible pair
    }

template<class _Other1,
    class _Other2,
    class = enable_if_t<is_constructible<_Ty1, _Other1>::value
                    && is_constructible<_Ty2, _Other2>::value>,
    enable_if_t<!is_convertible<_Other1, _Ty1>::value
            || !is_convertible<_Other2, _Ty2>::value, int> = 0>
    constexpr explicit pair(pair<_Other1, _Other2>&& _Right)
        _NOEXCEPT_OP((is_nothrow_constructible<_Ty1, _Other1>::value
            && is_nothrow_constructible<_Ty2, _Other2>::value))
    : first(_STD forward<_Other1>(_Right.first)),
        second(_STD forward<_Other2>(_Right.second))
    {   // construct from moved compatible pair
    }
VS 2017 第 206 行的

utility 文件, _Other1和_Other2是参数,这是std::pair的构造函数, 我们正在使用 Other1 和 Other2 来初始化“first”和“second”,

我觉得 is_constructible 已经足够了,为什么我们在这里使用 is_convertible 呢?
顺便问一下,class = enable_if_t&lt; ... ::value&gt;enable_if_t&lt; ... ::value,int&gt; = 0 有什么区别?

【问题讨论】:

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


【解决方案1】:

用于实现[pairs.pair]/12

除非is_­constructible_­v&lt;first_­type, U1&amp;&amp;&gt; 为真且此构造函数不应参与重载决议 is_­constructible_­v&lt;second_­type, U2&amp;&amp;&gt; 是真的。 当且仅当 is_­convertible_­v&lt;U1&amp;&amp;, first_­type&gt; 为 false 或 is_­convertible_­v&lt;U2&amp;&amp;, second_­type&gt; 是假的。

【讨论】:

  • 但是为什么如果其中一种或两种类型都不可转换,它应该是explicit
【解决方案2】:

我认为is_constructible 已经足够了,为什么我们在这里使用is_convertible

这里的目标是正确处理explicit 构造。考虑只做前者并尝试编写一个包装器(在此处使用REQUIRES 隐藏您想要的任何 SFINAE 方法):

template <class T>
class wrapper {
public:
    template <class U, REQUIRES(std::is_constructible<T, U&&>::value)>
    wrapper(U&& u) : val(std::forward<U>(u)) { }
private:
    T val;
};

如果我们只有这些,那么:

struct Imp { Imp(int ); };
struct Exp { explicit Exp(int ); };

Imp i = 0; // ok
Exp e = 0; // error
wrapper<Imp> wi = 0; // ok
wrapper<Exp> we = 0; // ok?!?

我们绝对不希望最后一个没问题 - 这打破了对Exp 的期望!

现在,如果可以从 U&amp;&amp; 直接初始化 T,则 s_constructible&lt;T, U&amp;&amp;&gt; 为真 - 如果 T(std::declval&lt;U&amp;&amp;&gt;()) 是有效表达式。

另一方面,

is_convertible&lt;U&amp;&amp;, T&gt; 检查是否可以从U&amp;&amp;复制-初始化T。也就是说,如果T copy() { return std::declval&lt;U&amp;&amp;&gt;(); } 有效。

不同的是,如果转换为explicit,则后者不起作用:

+-----+--------------------------+------------------------+
|     | is_constructible<T, int> | is_convertible<int, T> |
+-----+--------------------------+------------------------+
| Imp |        true_type         |       true_type        |
| Exp |        true_type         |       false_type       |
+-----+--------------------------+------------------------+

为了正确传播显式性,我们需要同时使用这两个特征 - 我们可以从中创建元特征:

template <class T, class From>
using is_explicitly_constructible = std::integral_constant<bool,
    std::is_constructible<T, From>::value &&
    !std::is_convertible<From, T>::value>;

template <class T, class From>
using is_implicitly_constructible = std::integral_constant<bool,
    std::is_constructible<T, From>::value &&
    std::is_convertible<From, T>::value>;

这两个特征是不相交的,所以我们可以编写两个绝对不可行的构造函数模板,其中一个构造函数是显式的,而另一个不是:

template <class T>
class wrapper {
public:
    template <class U, REQUIRES(is_explicitly_constructible<T, U&&>::value)>
    explicit wrapper(U&& u) : val(std::forward<U>(u)) { }

    template <class U, REQUIRES(is_implicitly_constructible<T, U&&>::value)>
    wrapper(U&& u) : val(std::forward<U>(u)) { }
private:
    T val;
};

这给了我们想要的行为:

wrapper<Imp> wi = 0; // okay, calls non-explicit ctor
wrapper<Exp> we = 0; // error
wrapper<Exp> we2(0); // ok

这就是实现在这里所做的——除了两个元特征之外,它们具有写出的所有条件explicitly。

【讨论】:

    猜你喜欢
    • 2011-03-24
    • 2016-06-13
    • 2015-01-07
    • 2011-08-30
    • 1970-01-01
    • 2010-09-11
    • 2012-11-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多