std :: optional'转发引用构造函数

时间:2017-12-25 05:43:33

标签: c++ overloading optional c++17

std::optional截至此日期有8个构造函数,如下所示(此处http://en.cppreference.com/w/cpp/utility/optional/optional

/* (1) */ constexpr optional() noexcept;
/* (1) */ constexpr optional( std::nullopt_t ) noexcept;

/* (2) */ constexpr optional( const optional& other );

/* (3) */ constexpr optional( optional&& other ) noexcept(/* see below */);

template < class U >
/* (4) */ /* EXPLICIT */ optional( const optional<U>& other );

template < class U >
/* (5) */ /* EXPLICIT */ optional( optional<U>&& other );

template< class... Args > 
/* (6) */ constexpr explicit optional( std::in_place_t, Args&&... args );

template< class U, class... Args >
/* (7) */ constexpr explicit optional( std::in_place_t,
                                       std::initializer_list<U> ilist, 
                                       Args&&... args );

template < class U = value_type >
/* (8) */ /* EXPLICIT */ constexpr optional( U&& value );

我喜欢上一个构造函数。它有助于std::optional从cv-ref限定的类型Type引用构造。哪个超级方便。

除此之外,最后一个构造函数也有帮助,因为它是一种使用列表初始化来初始化std::optional实例的便捷方式,而不必使用std::in_place。发生这种情况是因为当大括号括起来的参数列表被传递给构造函数时,会使用默认类型,因为函数模板不能从{}中推断出类型(至少这是我对情况的理解并且是一个整洁的我最近才拿到的技巧)(另请注意,这只能用于调用基础类型的非显式构造函数,根据此处的规则http://en.cppreference.com/w/cpp/language/list_initialization

auto optional = std::optional<std::vector<int>>{{1, 2, 3, 4}};

我能理解的最后一个构造函数有两个约束

  • std::decay_t<U>既不是std::in_place_t也不是std::optional<T>
  • 当且仅当std::is_convertible_v<U&&, T>为假
  • 时,此构造函数是显式的

第一个很容易理解,它有助于防止构造函数(2),(3),(4),(5),(6)和(7)的歧义。如果类型为std::in_place,则可能与(6)和(7)冲突。如果类型是std::optional的实例化,那么它可能与(2),(3),(4)和(5)冲突。

第二个&#34;前锋&#34;基础类型的构造函数对optional类型

的显式性

但第三个限制是好奇的

  • 除非std::is_constructible_v<T, U&&>为真,否则此构造函数不参与重载决策

为什么需要这个? (8)永远不会与空构造函数冲突,因为它至少需要一个参数。这只剩下一个原因 - 它在传递std::nullopt_t时可能与std::nullopt冲突,但这不会发生,因为nullopt版本总是更好的匹配,无论cv-ref限定版本是什么通过std::nullopt_t(如下所示)

void func(int) {
    cout << __PRETTY_FUNCTION__ << endl;
}

template <typename U>
void func(U&&) {
    cout << __PRETTY_FUNCTION__ << endl;
}

int main() {
    auto i = int{1};
    func(1);
    func(i);
    func(std::move(i));
    func(std::as_const(i));
    func(std::move(std::as_const(i)));
}

最后一次限制背后的原因是什么?

为什么不像往常一样让构造函数出错?这是否需要帮助检测类型是否可以通过SFINAE传递的参数构造,而不会在以后导致硬错误?

2 个答案:

答案 0 :(得分:5)

说谎的特质是不好的。

基本词汇类型的谎言特征是plusungood。

基本词汇类型的特征,也很容易干扰重载解析,这是双倍的。

void f(std::optional<int>);
void f(std::optional<const char*>);
f({""}); // ambiguous without the constraint

答案 1 :(得分:1)

我在发布问题时就得到了答案(我的问题的最后部分包含在编辑中)

库希望为用户提供一种方便的方法,以便能够检测是否可以在SFINAE情况下通过该构造函数构造可选项,而不会在以后导致硬错误。如果未包含该约束,那么库可能会认为可选的任何其他类型都可以构造,但后来发现它会导致硬错误。

例如,以下示例说明了类似的问题(https://wandbox.org/permlink/XGgWgJcNJ99BBycu

#include <iostream>
#include <string>
#include <vector>
#include <tuple>

using std::cout;
using std::endl;

class Something {
public:
    Something() = default;
    template <typename U>
    Something(U&& u) : vec(std::forward<U>(u)) {}
private:
    std::vector<int> vec;
};

template <typename U, typename = std::void_t<>>
class SomethingConstructibleFrom 
    : public std::false_type {};
template <typename U>
class SomethingConstructibleFrom<
        U, 
        std::void_t<decltype(Something{std::declval<U>()})>> 
    : public std::true_type {};

int main() {
    if constexpr (SomethingConstructibleFrom<std::string>::value) {
        // this must be constructible because the above returned true
        // but SURPRISE!
        auto something = Something{std::string{"qwerty"}};
        std::ignore = something; 
    }
}