【问题标题】:How to define a copy constructor for a const template parameter?如何为 const 模板参数定义复制构造函数?
【发布时间】:2016-02-10 07:28:21
【问题描述】:

我正在创建自定义迭代器,但无法满足创建 const 迭代器并使用非const begin() 对其进行初始化的场景。根据 STL,这是合法的,可以用 std::string 来证明:

#include <string>

using namespace std;

int main() {
    string::iterator a;
    string::const_iterator b = a;

    return 0;
}

我不知道如何使它工作:

template<typename T>
class some_class {
};

int main() {
    some_class<int> a;

    // Works OK!
    const some_class<int> const_b = a;

    // error: conversion from 'some_class<int>' to non-scalar type 'const some_class<const int>'
    const some_class<const int> const_c = a;

    return 0;
}

更新

@ALEXANDER KONSTANTINOV 提供了一个解决方案,但它不能满足所有可能的 STL 测试用例。接下来我正在测试@Bo Persson 的建议。将构造函数更改为 const some_class&lt;U&gt;&amp; other 将允许它编译,但随后 iterator a = const_iterator b 也会错误地为真。

#include <string>

using namespace std;

template<typename T>
class some_class {
public:
    some_class() {
    }

    template<typename U>
    some_class(some_class<U>& other) {
    }
};

namespace name {
    typedef some_class<int> iterator;
    typedef const some_class<const int> const_iterator;
}

int main() {
    string::iterator a;
    string::const_iterator b = a;
    const string::iterator c;
    string::iterator d = c;
    string::const_iterator e = c;

    name::iterator f;
    name::const_iterator g = f;
    const name::iterator h;
    name::iterator i = h;
    name::const_iterator j = h; // <- Error

    return 0;
}

更新

在构造函数中添加const 似乎有些混乱。这是一个测试用例:

// This is not allowed by the STL
//string::const_iterator _a;
//string::iterator _b = _a; // <- Error!

// This should NOT compile!
name::const_iterator _a;
name::iterator _b = _a;

【问题讨论】:

  • 你检查过标准库是如何实现的吗?
  • SFINAE 结合std::remove_conststd::is_same 让您有条件地提供合适的构造函数。
  • 一种流行的方法是使const_iterator 成为iterator 的基类。这样就存在从派生到基的隐式转换。
  • @Bo,您的建议似乎按预期工作,并完全回答了这个问题。我是否遗漏了任何警告或者这是否可靠?否则请作为答案提交。

标签: c++ stl constants std copy-constructor


【解决方案1】:

首先 - 你不能假设 std::string::const_iterator 只是常规迭代器的“常量版本” - 就像这个 const std::string::iterator

当您查看您的 STL 库实现时(这只是来自 gcc4.9.2 基本字符串的 STL 标头的示例):

  typedef __gnu_cxx::__normal_iterator<pointer, basic_string>  iterator;
  typedef __gnu_cxx::__normal_iterator<const_pointer, basic_string>
                                                        const_iterator;

正如你所看到的 - 两个迭代器的不同之处在于返回指针值 - pointerconst_pointer - 就是这种情况 - “const iterator”不是不能改变的东西 - 而是返回 const 指针/的东西引用,因此您不能修改迭代器迭代的值。

所以 - 我们可以进一步调查,看看如何实现从非 const 版本到 const 版本的所需复制:

  // Allow iterator to const_iterator conversion
  template<typename _Iter>
    __normal_iterator(const __normal_iterator<_Iter,
          typename __enable_if<
           (std::__are_same<_Iter, typename _Container::pointer>::__value),
          _Container>::__type>& __i) _GLIBCXX_NOEXCEPT
    : _M_current(__i.base()) { }

所以,基本上 - 此构造函数接受同一模板的任何实例 (__normal_iterator) - 但它具有 enable_if 闭包以仅允许 const 指针的实例。

我相信你也会这样做

  1. 拥有真正的 const_iterator - 不仅仅是常规迭代器的 const 版本
  2. 并且具有来自 const_iterator 的模板构造函数和 enable_if 限制,以禁止从任何东西构造(我的意思是从迭代器到 std::strings 的 int 上的迭代器)

根据你的例子:

#include <type_traits>

template<typename T>
class some_class {
public:
    some_class() {
    }

    template <typename U>
    using allowed_conversion_from_non_const_version = std::enable_if_t<std::is_same<std::remove_cv_t<T>,U>::value>;

    template<typename U, typename EnableIf = allowed_conversion_from_non_const_version<U>>
    some_class(const some_class<U>&) {
    }

    template<typename U, typename EnableIf = allowed_conversion_from_non_const_version<U>>
    some_class& operator = (const some_class<U>&) {
    }
};

从这个例子中可以看出两点:

  1. 还需要赋值运算符
  2. 您只能从非 const 版本启用到 const 版本 - 这是通过 enable_if/remove_cv 的组合实现的(remove_const 也可以 - 但为什么不构建 volatile 版本 - 无论如何cv 更短比const)

【讨论】:

  • 为了简洁起见,我在示例中使用了intconst int 来替代pointerconst_pointer。使用@Bo Persson 的建议进行的初步测试似乎适用于测试用例,但我需要进一步调查它和您的建议。我不得不离开,但您的建议似乎提供了相同的解决方案,但不需要继承,并且还从 const_iterator typedef 中删除了 const
  • 我无法在我的测试用例中完成这项工作。你能举个例子吗?这似乎类似于@Tony D 的建议。
  • @Zhro - 完成。我检查它在 clang 和 gcc 下编译得很好。
  • 看起来不错。非常感谢;这对我来说是一个非常有价值的话题。
【解决方案2】:

你应该为你的类定义一个模板拷贝构造函数

template<typename T>
class some_class {

public:
    template<typename U> friend class some_class;

    some_class()
    {
    }

    template<typename U>
    some_class(const some_class<U> &other)
    : data(other.data)
    {}

private:
    T* data;

};

int main() {
    some_class<int> a;

    // Works OK!
    const some_class<int> const_b = a;

    // error: conversion from 'some_class<int>' to non-scalar type 'const some_class<const int>'
    const some_class<const int> const_c = a;

    return 0;
}

【讨论】:

  • 这使代码编译,但也允许您将 const_iterator 转换为非 const 迭代器。
  • 不按此:ideone.com/aHwLALerror: conversion from 'const some_class&lt;const int&gt;' to non-scalar type 'some_class&lt;int&gt;' requested
  • 但是您也不能使用 STL 迭代器将 const const_iterator 转换为非常量迭代器。 ideone.com/aEpNb8
  • 我正在考虑将some_class&lt;const int&gt; 转换为some_class&lt;int&gt;,这样您就可以修改intideone.com/4JaCiZ
  • 我认为您的问题是如何将 non_const 迭代器转换为 const,这在 STL 中是可能的。但不可能向后转换 STL 迭代器。
【解决方案3】:

复制构造函数应该有一个 const 引用并且这段代码会编译

some_class(const some_class<U>& other){}

【讨论】:

  • 将构造函数更改为 const some_class& other 将允许它编译,但迭代器 a = const_iterator b 也会错误地为真(如 OP 中所述)。
  • 是的,但它允许将const 对象分配给非const 对象。 STL 迭代器不允许这样做。
猜你喜欢
  • 2022-01-18
  • 1970-01-01
  • 2017-05-10
  • 2011-05-24
  • 1970-01-01
  • 2016-09-12
  • 2020-01-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多