web-dev-qa-db-ja.com

const_iteratorのconstnessを削除する方法は?

この質問の拡張として const_iteratorsは速いですか?const_iteratorsについて別の質問があります。 const_iteratorの恒常性を取り除く方法は?イテレータは一般化された形式のポインタですが、それでもconst_iteratoriteratorsは2つの異なるものです。したがって、const_cast<>を使用してconst_iteratorからiteratorsに変換することもできないと思います。

1つのアプローチは、const_iteratorが指す要素まで移動するイテレータを定義することです。しかし、これは線形時間アルゴリズムのように見えます。

これを達成するための最良の方法は何ですか?

42
aJ.

C++ 11には、一定の時間計算量を持つソリューションがあります。任意のシーケンス、連想、または順序付けられていない連想コンテナ(すべての標準ライブラリコンテナを含む)に対して、空の範囲で範囲消去メンバー関数を呼び出すことができます。

template <typename Container, typename ConstIterator>
typename Container::iterator remove_constness(Container& c, ConstIterator it)
{
    return c.erase(it, it);
}

範囲消去メンバー関数には、1対のconst_iteratorパラメーターがありますが、それらはiteratorを返します。空の範囲が指定されているため、eraseを呼び出してもコンテナの内容は変更されません。

このトリックについては、ハワード・ヒナントとジョン・カルブへのハットチップ。

68
James McNellis

残念ながら、線形時間はそれを行う唯一の方法です。

iter i(d.begin());
advance (i,distance<ConstIter>(i,ci));

ここで、iterとconstIterは適切なtypedefであり、dは反復するコンテナーです。

14
PaulJWilliams

以前の投稿への回答には、パフォーマンスに関係のない理由で、代わりにconst_iteratorsを使用することを推奨する人が何人かいました。可読性、デザインボードからコードまでのトレーサビリティ... const_iteratorsを使用して非const要素への変更アクセスを提供することは、const_iteratorsをまったく使用しないよりもはるかに悪いです。あなたは自分のコードを自分だけが理解できるものに変換しているのですが、デザインが悪く、保守性に問題があります。 constをキャストするためだけに使用することは、constをまったく使用しないよりもはるかに悪いことです。

あなたがそれを望んでいると確信しているなら、C++の良い/悪い部分は、あなたがいつでも自分を吊るすのに十分なロープを手に入れることができるということです。パフォーマンスの問題にconst_iteratorを使用することを意図している場合は、それを再考する必要がありますが、それでも足を踏み外したい場合は... C++が最適な武器を提供します。

まず、最も単純です。操作が引数をconstとして受け取る場合(内部でconst_castを適用する場合でも)、ほとんどの実装で直接機能するはずです(おそらく未定義の動作であっても)。

ファンクターを変更できない場合は、どちらの側からでも問題に取り組むことができます。constイテレーターの周りに非constイテレーターラッパーを提供するか、非constファンクターの周りにconstファンクターラッパーを提供します。

イテレータファサード、長い道のり:

template <typename T>
struct remove_const
{
    typedef T type;
};
template <typename T>
struct remove_const<const T>
{
    typedef T type;
};

template <typename T>
class unconst_iterator_type
{
    public:
        typedef std::forward_iterator_tag iterator_category;
        typedef typename remove_const<
                typename std::iterator_traits<T>::value_type
            >::type value_type;
        typedef value_type* pointer;
        typedef value_type& reference;

        unconst_iterator_type( T it )
            : it_( it ) {} // allow implicit conversions
        unconst_iterator_type& operator++() {
            ++it_;
            return *this;
        }
        value_type& operator*() {
            return const_cast<value_type&>( *it_ );
        }
        pointer operator->() {
            return const_cast<pointer>( &(*it_) );
        }
        friend bool operator==( unconst_iterator_type<T> const & lhs,
                unconst_iterator_type<T> const & rhs )
        {
            return lhs.it_ == rhs.it_;
        }
        friend bool operator!=( unconst_iterator_type<T> const & lhs,
                unconst_iterator_type<T> const & rhs )
        {
            return !( lhs == rhs );
        }
    private:
        T it_;  // internal (const) iterator
};

Scott Meyerの記事 const_iteratorsよりもイテレータを優先することについてこれに答えます。 Visageの答えは、C++ 11より前の唯一の安全な代替手段ですが、実際には、適切に実装されたランダムアクセスイテレータの場合は一定時間であり、他の場合は線形時間です。

4
Pontus Gagge

これはあなたが望んでいた答えではないかもしれませんが、いくらか関連しています。

イテレータが指すものを変更したいと思います。私が行う最も簡単な方法は、代わりに返された参照をconst_castすることです。

このようなもの

const_cast<T&>(*it);

3
leiz

この変換は、適切に設計されたプログラムでは必要ないと思います。

これを行う必要がある場合は、コードを再設計してみてください。

回避策として、次に実行できます。

typedef std::vector< size_t > container_type;
container_type v;
// filling container code 
container_type::const_iterator ci = v.begin() + 3; // set some value 
container_type::iterator i = v.begin();
std::advance( i, std::distance< container_type::const_iterator >( v.begin(), ci ) );

ただし、アルゴリズムがコンテナにアクセスできないため、この変換が不可能な場合があると思います。

2
bayda

Const_iteratorからbegin()イテレータを減算して、const_iteratorが指している位置を取得し、begin()をその位置に追加して、非constイテレータを取得できます。これは非線形コンテナではあまり効率的ではないと思いますが、ベクトルなどの線形コンテナでは一定の時間がかかります。

vector<int> v;                                                                                                         
v.Push_back(0);
v.Push_back(1);
v.Push_back(2);
v.Push_back(3);
vector<int>::const_iterator ci = v.begin() + 2;
cout << *ci << endl;
vector<int>::iterator it = v.begin() + (ci - v.begin());
cout << *it << endl;
*it = 20;
cout << *ci << endl;

[〜#〜] edit [〜#〜]:これは線形(ランダムアクセス)コンテナでのみ機能するようです。

1
marcog

constイテレータ値ポインタを非const値ポインタに変換し、次のように直接使用できます。

    vector<int> v;                                                                                                         
v.Push_back(0);
v.Push_back(1);
v.Push_back(2);
v.Push_back(2);
vector<int>::const_iterator ci = v.begin() + 2;
cout << *ci << endl;
*const_cast<int*>(&(*ci)) = 7;
cout << *ci << endl;
0
Ankit

標準ライブラリになく、erase()メソッドを含まないコンテナで機能するこれに対する解決策を考え出すのは楽しいだろうと思いました。

これを使用しようとすると、Visual Studio2013のコンパイルがハングします。インターフェースをすぐに理解できる読者にテストケースを任せるのは良い考えのように思われるので、テストケースは含めません。なぜこれがコンパイルでハングするのかわかりません。これは、const_iteratorがbegin()と等しい場合でも発生します。

// deconst.h

#ifndef _miscTools_deconst
#define _miscTools_deconst

#ifdef _WIN32 
    #include <Windows.h>
#endif

namespace miscTools
{
    template < typename T >
    struct deconst
    {

        static inline typename T::iterator iterator ( typename T::const_iterator*&& target, T*&& subject )
        {
            typename T::iterator && resultant = subject->begin ( );

            bool goodItty = process < 0, T >::step ( std::move ( target ), std::move ( &resultant ), std::move ( subject ) );

        #ifdef _WIN32
             // This is just my habit with test code, and would normally be replaced by an assert
             if ( goodItty == false ) 
             {
                  OutputDebugString ( "     ERROR: deconst::iterator call. Target iterator is not within the bounds of the subject container.\n" ) 
             }
        #endif
            return std::move ( resultant );
        }

    private:

        template < std::size_t i, typename T >
        struct process
        {
            static inline bool step ( typename T::const_iterator*&& target, typename T::iterator*&& variant, T*&& subject )
            {
                if ( ( static_cast <typename T::const_iterator> ( subject->begin () + i ) ) == *target )
                {
                    ( *variant ) += i;
                    return true;
                }
                else
                {
                    if ( ( *variant + i ) < subject->end () )
                    {
                        process < ( i + 1 ), T >::step ( std::move ( target ), std::move ( variant ), std::move ( subject ) );
                    }
                    else { return false; }
                }
            }
        };
    };
}

#endif
0
user2813810