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

我的未来我决定 提交于 2019-12-10 21:52:27

问题


I'm creating a custom iterator and I'm having trouble satisfying the scenario where I create a const iterator and initialize it with a non-const begin(). This is legal according to the STL and can be demonstrated with std::string:

#include <string>

using namespace std;

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

    return 0;
}

I can't figure out how to make it work:

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

UPDATE

@ALEXANDER KONSTANTINOV provided a solution but it does not satisfy all possible STL test cases. I'm testing @Bo Persson's suggestion next. Changing the constructor to const some_class<U>& other will allow it to compile but then iterator a = const_iterator b would also erroneously be true.

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

UPDATE

There seems to be some confusion regarding adding const to the constructor. Here is a test case:

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

回答1:


First of all - you cannot assume that std::string::const_iterator is just "const version" of regular iterator - like this const std::string::iterator.

When you look at your STL library implementation (this is just example from gcc4.9.2 STL header for basic_string):

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

As you can see - what differs both iterators is the return pointer value - pointer vs const_pointer - and that is the case - "const iterator" is not something that cannot change - but something that returns const pointer/references so you cannot modify the values the iterator iterates over.

So - we can investigate further and see how the desired copying from non const to const version was achieved:

  // 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()) { }

So, basically - this constructor accepts any instance of the same template (__normal_iterator) - but it has enable_if closure to allow only instance of const pointer.

I believe you shall do the same in your case

  1. Have real const_iterator - not just const version of regular iterator
  2. And have template constructor from const_iterator with enable_if restriction to disallow constructing from anything (I mean iterator over ints from iterator over std::strings)

As per your example:

#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>&) {
    }
};

Two things to read from this example:

  1. Assignment operator is also needed
  2. You shall enable only from non-const to const version - and this is achieved by combination of enable_if/remove_cv (remove_const also works - but why not construct also volatile version - anyway cv is shorter than const)



回答2:


You should define a template copy constructor for your class

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



回答3:


Copy constructor should have a const reference and this code will compile

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


来源:https://stackoverflow.com/questions/35309197/how-to-define-a-copy-constructor-for-a-const-template-parameter

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!