如何为 const 模板参数定义复制构造函数?

How to define a copy constructor for a const template parameter?

提问人:Zhro 提问时间:2/10/2016 最后编辑:PiotrNyczZhro 更新时间:1/18/2018 访问量:263

问:

我正在创建一个自定义迭代器,但我无法满足创建迭代器并使用非 .根据 STL,这是合法的,可以用 std::string 来证明:constconstbegin()

#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<U>& otheriterator 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;
C++ STL 常量 std copy-constructor

评论

0赞 Ulrich Eckhardt 2/10/2016
您是否检查过标准库是如何实现这一点的?
0赞 Tony Delroy 2/10/2016
SFINAEstd::remove_const 结合使用,并允许您有条件地提供合适的构造函数。std::is_same
1赞 Bo Persson 2/10/2016
一种流行的方法是创建一个基类。这样就有了从派生到基的隐式转换。const_iteratoriterator
0赞 Zhro 2/10/2016
@Bo,您的建议似乎按预期工作,并完全回答了问题。我是否遗漏了任何警告,或者这是否可靠?否则,请提交作为答案。

答:

0赞 ALEXANDER KONSTANTINOV 2/10/2016 #1

您应该为类定义一个模板复制构造函数

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;
}

评论

0赞 Bo Persson 2/10/2016
这使得代码易于编译,但也允许您将const_iterator转换为非常量迭代器。
0赞 Zhro 2/10/2016
不是根据这个:ideone.com/aHwLAL error: conversion from 'const some_class<const int>' to non-scalar type 'some_class<int>' requested
0赞 ALEXANDER KONSTANTINOV 2/10/2016
但是,您也不能使用 STL 迭代器将 const const_iterator转换为非常量迭代器。ideone.com/aEpNb8
0赞 Bo Persson 2/10/2016
我正在考虑将允许您修改 .ideone.com/4JaCiZsome_class<const int>some_class<int>int
0赞 ALEXANDER KONSTANTINOV 2/10/2016
我以为你的问题是如何将non_const迭代器转换为 const,这在 STL 中是可能的。但是,向后转换 STL 迭代器是不可能的。
-1赞 Alexander Chernin 2/10/2016 #2

Copy 构造函数应该有一个 const 引用,此代码将编译

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

评论

0赞 Zhro 2/10/2016
将构造函数更改为 const some_class<U>& other 将允许它编译,但迭代器 a = const_iterator b 也会错误地为真(如 OP 中所述)。
0赞 Zhro 2/10/2016
是的,但它允许将对象分配给非对象。STL 迭代器不允许这样做。constconst
2赞 PiotrNycz 2/10/2016 #3

首先 - 你不能假设这只是常规迭代器的“常量版本” - 像这样 .std::string::const_iteratorconst std::string::iterator

当您查看 STL 库实现时(这只是 gcc4.9.2 STL 标头中的示例,用于 basic_string):

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

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

因此,我们可以进一步调查,看看如何实现从 non 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()) { }

所以,基本上 - 这个构造函数接受同一模板 () 的任何实例 - 但它有闭包,只允许 const 指针的实例。__normal_iteratorenable_if

我相信你也会在你的情况下做同样的事情

  1. 拥有真正的const_iterator - 而不仅仅是常规迭代器的 const 版本
  2. 并让来自const_iterator的模板构造函数具有enable_if限制,以禁止从任何内容进行构造(我的意思是迭代器而不是 ints 来自 iterator over std::strings)

根据您的示例:

#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. 您只能启用从 non-const 到 const 版本 - 这是通过组合 / ( 也有效 - 但为什么不构造 volatile 版本 - 无论如何都比enable_ifremove_cvremove_constcvconst)

评论

0赞 Zhro 2/10/2016
在我的示例中,我使用 和 作为简洁的替代品。使用 @Bo Persson 的建议进行初步测试似乎适用于测试用例,但我需要进一步调查它和您的建议。我必须离开,但似乎您的建议提供了相同的解决方案,但不需要继承,并且还从 typedef 中删除了。intconst intpointerconst_pointerconstconst_iterator
0赞 Zhro 2/10/2016
我无法用我的测试用例来完成这项工作。你能举个例子吗?这似乎与@Tony D 的建议相似。
0赞 PiotrNycz 2/10/2016
@Zhro - 完成。我检查它在 clang 和 gcc 下编译良好。
0赞 Zhro 2/10/2016
看起来很棒。非常感谢;这对我来说是一个非常有价值的线索。