【问题标题】:Const reference wrapper from reference wrapper来自引用包装器的 const 引用包装器
【发布时间】:2015-12-04 15:46:29
【问题描述】:

考虑以下引用包装器:

template <class T>
struct wrapper
{
    wrapper(T& x): reference{x} {}
    void set(const T& x) {reference = x;}
    T& get() const {return reference;}
    T& reference;
};

我想知道:

  • 如何仅通过模板别名声明 const 引用包装器 template &lt;class T&gt; using const_wrapper = /* const wrapper&lt;T&gt; or wrapper&lt;const T&gt;?*/
  • 如果在这种状态下不可能,如何更改包装器结构以使前面的点成为可能?
  • 如何解决以下问题:int i = 42; wrapper&lt;const char&gt; w(i);会编译但不工作(我想阻塞构造函数)
  • 究竟是什么问题,iteratorconst_iterator 一般有两种不同的实现方式?

【问题讨论】:

  • 您不使用std::reference_wrapper的原因是什么?
  • 这个例子旨在了解发生了什么。如果我想要一个引用包装器,我会使用 std::reference_wrapper。
  • "如何仅通过模板别名声明 const 引用包装器 template &lt;class T&gt; using const_wrapper = /* const wrapper&lt;T&gt; or wrapper&lt;const T&gt;?*/" 你是说 const(引用包装器)还是(const 引用)包装器?甚至是 const(const 引用)包装器?
  • 另一个问题:当 set(const T&amp;) 被调用时,你希望你的 const 引用包装器做什么?
  • @SimonKraemer: set(const T&) 应该设置引用的值。

标签: c++ c++11 reference constants template-classes


【解决方案1】:

如何通过仅模板别名模板 &lt;class T&gt; using const_wrapper = /* const wrapper&lt;T&gt; or wrapper&lt;const T&gt;?*/ 声明 const 引用包装器

显然是这样的:

template <class T> using const_wrapper = wrapper<const T>;

包含的类型是const,而不是包装器。

但是请注意,如果 Tconst,则无法调用您的 set 函数。这是出于显而易见的原因;您无法更改 const 值。

如何解决以下问题:int i = 42; wrapper&lt;const char&gt; w(i);会编译但不工作(我想阻塞构造函数)

这实际上有点复杂。如果用户尝试使用与T 不完全匹配的类型,您需要做的是导致编译失败。为此,您需要使用 C++11 的 =delete 功能:

template <class T>
struct wrapper
{
    wrapper(T& x): reference{x} {}
    template<typename U>
        wrapper(const U &) = delete;
    //...
};

当有人传递与T 不完全匹配的类型时,将使用第二个构造函数。因为它是deleted,所以当人们尝试使用它时会出现编译器错误。

对于什么具体问题,iterator 和 const_iterator general 有两种不同的实现方式?

谁说他们这样做?它们甚至不需要是不同的 类型(例如考虑set 的迭代器),更不用说要求有不同的实现。它们只是不同的类型别名。

【讨论】:

  • 我不认为wrapper&lt;const T&gt;; 是正确的。您不能更新引用本身(请参阅有关 set(const T&amp;) 的 cmets)。如果这是 OP 想要做的,他需要将代码更改为使用指针作为成员 - 正如 std::reference_wrapper 所做的那样。
  • @SimonKraemer:他想知道如何将const 应用于他的容器所包含的类型;我发布的内容会这样做。我从未声称在他的容器中定义的所有功能仍然工作。此外,尚不清楚他究竟想要他的 set 函数做什么。
  • vector&lt;const int&gt; 是不允许的,我想。 set&lt;int&gt; 是一个更好的例子。
【解决方案2】:
  • 如何仅通过模板别名声明 const 引用包装器 template &lt;class T&gt; using const_wrapper = /* const wrapper&lt;T&gt; or wrapper&lt;const T&gt;?*/

这取决于你想表达的意思,你还没有说清楚。

  • 如果在这种状态下不可能,如何更改包装器结构以使前面的点成为可能?

见上文。

  • 如何解决以下问题:int i = 42; wrapper&lt;const char&gt; w(i);会编译但不工作(我想阻塞构造函数)

添加部分特化

template <class T>
struct wrapper<const T>
{
    wrapper(const T& x): reference{x} {}
    wrapper(T&) = delete;
    void set(const T& x) {reference = x;}
    const T& get() const {return reference;}
    const T& reference;
};

或者向主模板添加一个适当约束的构造函数:

template <class T>
struct wrapper
{
    wrapper(T& x): reference{x} {}
    template<typename U, typename = std::enable_if_t<std::is_const<T>{} && std::is_same<T, const U>{}, void>>
    wrapper(U&) = delete;
    void set(const T& x) {reference = x;}
    T& get() const {return reference;}
    T& reference;
};
  • 具体什么问题,iteratorconst_iterator 一般有两种不同的实现方式?

通常应该可以将iterator 隐式转换为const_iterator,但反之则不行。因此,您要么需要一个仅存在于 const_iterator 表单的 SFINAEd 构造函数,要么有两种实现,一种支持额外转换,另一种不支持。

使用 SFINAE 在 C++98 中不是一个选项,因此大多数标准库实现(早在 Expression SFINAE 可用之前就编写好了)使用两个单独的实现。

【讨论】:

    【解决方案3】:

    假设set 应该改变被引用对象的值而不是引用本身:

    template <class T>
    struct wrapper
    {
        wrapper(T& x) : reference{ x } {}
    
        template<typename U> 
        wrapper(U& x) : reference{ x } {} //<- Possible answer for question 3
    
        //template<typename U>
        //wrapper(U& x) = delete; //<- Possible answer for question 3
    
        void set(const T& x) const { reference = x; } //<- Answer for question 2
        T& get() const { return reference; }
    
        T& reference;
    };
    
    template <class T>
    using const_wrapper = const wrapper<T>; //<- Answer for question 1
    
    
    int main()
    {
        //Testing const_wrapper
        char a;
        const_wrapper<char> b(a);
        b.set('a');
    
        //Testing narrowing cast
        int i = 42; 
        wrapper<const char> w(i);   //error C2397: conversion from 'int' to 'const char' requires a narrowing conversion
                                    //error C2280 : 'wrapper<const char>::wrapper<int>(U &)' : attempting to reference a deleted function
        //w.set('b'); //error C3490: 'reference' cannot be modified because it is being accessed through a const object
    }
    

    对于你的最后一个问题,我无法给你一个满意的答案。这几乎是一个实现细节。 我想不出iteratorconst_iterator 至少没有相互继承的任何一般情况——非const 版本扩展了const 版本。

    更新: 最后一部分可能是 MSVC 特定的实现细节。我仍然认为从const_iterator 继承iterator 是合理的,但应该指出这一点。

    【讨论】:

    • 我想不出const_iterator继承自iterator的任何情况。
    • @JonathanWakely 反过来。 “扩展 const 版本的非 const 版本。”所以iterator继承自const_iterator
    • 我也从未见过这样的做法,所以我能想到 很多 种情况,它们“至少没有相互继承”。
    • @JonathanWakely 例如std::string::iterator 的 MSVC 实现。类型定义为typedef typename _Mybase::iterator iterator;typedef typename _Mybase::const_iterator const_iterator;_MyBase_String_alloc,我们有 typedef _String_iterator&lt;_String_val&lt;_Val_types&gt; &gt; iterator;typedef _String_const_iterator&lt;_String_val&lt;_Val_types&gt; &gt; const_iterator;。而_String_iterator 定义为template&lt;class _Mystr&gt; class _String_iterator : public _String_const_iterator&lt;_Mystr&gt;
    • @JonathanWakely 让iterator 继承const_iterator 也很有意义,因此您可以将iterator 类型的对象作为参数传递给需要const_iterator 的参数。而且您没有重复的代码。你只需要添加非常量函数。
    猜你喜欢
    • 2020-08-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-21
    • 1970-01-01
    相关资源
    最近更新 更多