同様のconstメンバー関数と非constメンバー関数の間のコードの重複を削除するにはどうすればよいですか?


242

class X内部メンバーにアクセスを戻したい場所が次のようになっているとします。

class Z
{
    // details
};

class X
{
    std::vector<Z> vecZ;

public:
    Z& Z(size_t index)
    {
        // massive amounts of code for validating index

        Z& ret = vecZ[index];

        // even more code for determining that the Z instance
        // at index is *exactly* the right sort of Z (a process
        // which involves calculating leap years in which
        // religious holidays fall on Tuesdays for
        // the next thousand years or so)

        return ret;
    }
    const Z& Z(size_t index) const
    {
        // identical to non-const X::Z(), except printed in
        // a lighter shade of gray since
        // we're running low on toner by this point
    }
};

2つのメンバー関数X::Z()X::Z() const中かっこ内に同じコードがあります。これは重複したコードであり、複雑なロジックを持つ長い関数のメンテナンスの問題を引き起こす可能性があります。

このコードの重複を回避する方法はありますか?


この例では、constケースで値を返すため、以下のリファクタリングはできません。int Z()const {return z; }
マットプライス

1
基本的なタイプについては、あなたは完全に正しいです!私の最初の例はあまりよくありませんでした。代わりに、クラスインスタンスを返すとしましょう。(私はこれを反映するように質問を更新しました。)
ケビン

回答:


189

詳細な説明については、「で重複を避けるため、見出しを参照してくださいconstと非constPに、メンバ関数」。23、項目3の「const可能な限り使用する」、Effective C ++、3d、Scott Meyers 、ISBN-13:9780321334879。

代替テキスト

これがマイヤーズのソリューションです(簡略化):

struct C {
  const char & get() const {
    return c;
  }
  char & get() {
    return const_cast<char &>(static_cast<const C &>(*this).get());
  }
  char c;
};

2つのキャストと関数呼び出しは醜いかもしれませんが、それは正しいです。マイヤーズはその理由を徹底的に説明しています。


45
誰もいない今までにスコット・マイヤーズを以下のために解雇された:-)
スティーブ・ジェソップ

11
witkampは正しいですが、一般的にconst_castを使用することは悪いことです。マイヤーズが説明するように、これはそうではない特定のケースです。@Adam:ROM => constで結構です。const == ROMは、誰でもnon-constをconst willy-nillyにキャストできるため、明らかにナンセンスです。何かを変更しないことを選択するのと同じです。
スティーブジェソップ

44
タイプを誤って変更するのを防ぐため、一般に、staticの代わりにconst_castを使用してconstを追加することをお勧めします。
グレッグロジャース

6
@HelloGoodbye:Meyersは、クラスインターフェイスの設計者からのわずかな知能を想定していると思います。get()constがconstオブジェクトとして定義されたものを返す場合、の非constバージョンはありませんget()。実際、これに対する私の考えは時間とともに変化しました。テンプレートソリューションは重複を回避、コンパイラチェックのconst-correction 取得する唯一の方法であるため、個人的にはconst_castコードの重複を避けるためにを使用しません。複製されたコードを関数テンプレートに組み込むか、そのままにしておきます。
スティーブジェソップ2013

7
次の2つのテンプレートは、このソリューションの読みやすさで非常に役立ちます。template<typename T> const T& constant(T& _) { return const_cast<const T&>(_); }template<typename T> T& variable(const T& _) { return const_cast<T&>(_); }。次に、あなたが行うことができます:return variable(constant(*this).get());
ケーシーRodarmor

64

はい、コードの重複を回避することは可能です。constメンバー関数を使用してロジックを作成し、非constメンバー関数がconstメンバー関数を呼び出して、戻り値を非const参照(または関数がポインターを返す場合はポインター)に再キャストする必要があります。

class X
{
   std::vector<Z> vecZ;

public:
   const Z& z(size_t index) const
   {
      // same really-really-really long access 
      // and checking code as in OP
      // ...
      return vecZ[index];
   }

   Z& z(size_t index)
   {
      // One line. One ugly, ugly line - but just one line!
      return const_cast<Z&>( static_cast<const X&>(*this).z(index) );
   }

 #if 0 // A slightly less-ugly version
   Z& Z(size_t index)
   {
      // Two lines -- one cast. This is slightly less ugly but takes an extra line.
      const X& constMe = *this;
      return const_cast<Z&>( constMe.z(index) );
   }
 #endif
};

注:あなたが行うことが重要ですしない非const関数のロジックを入れてのconst関数呼び出しに非constの機能を持っている-それは未定義の動作になることがあります。その理由は、定数クラスインスタンスが非定数インスタンスとしてキャストされるためです。非constメンバー関数は誤ってクラスを変更する可能性があり、C ++標準の状態では未定義の動作が発生します。


3
うわー...それは恐ろしいことです。コードの量を増やし、明確さを減らし、2つの不快なconst_cast <> を追加しました。おそらく、これが実際に理にかなっている例を覚えていますか?
Shog9 2008

14
ちょっとこれはダメです!醜いかもしれませんが、スコットマイヤーズによると、それは(ほとんど)正しい方法です。参照してください効果的なC ++のconstと非コストのメンバ関数で重複を回避」の見出しの下に、第3版、第3項を。
jwfearn

17
解決策は醜いかもしれませんが、何を返すかを決定するコードが50行であると想像してください。その場合、特にコードをリファクタリングする必要がある場合、複製は非常に望ましくありません。私はこれまで何度も出会いました。
ケビン

8
thisとMeyersの違いは、Meyersにstatic_cast <const X&>(* this)があることです。const_castはconstを削除するためのものであり、追加するためのものではありません。
スティーブジェソップ

8
@VioletGiraffeこれは、オブジェクトが非constオブジェクトの非constメンバーであるため、オブジェクトが元々constで作成されたのではないことを知っています。コンパイラーはこの推論を行わず、保守的な規則に従います。このような状況でない場合、なぜconst_castが存在すると思いますか?
Caleth

47

C ++ 17はこの質問の最良の回答を更新しました:

T const & f() const {
    return something_complicated();
}
T & f() {
    return const_cast<T &>(std::as_const(*this).f());
}

これには次のような利点があります。

  • 何が起こっているのかは明らかです
  • コードのオーバーヘッドが最小限-1行に収まる
  • 間違いを犯しにくい(volatile偶然キャストすることはできますvolatileが、まれな修飾子です)

完全な控除ルートを使いたい場合は、ヘルパー関数を使用することで達成できます。

template<typename T>
constexpr T & as_mutable(T const & value) noexcept {
    return const_cast<T &>(value);
}
template<typename T>
constexpr T * as_mutable(T const * value) noexcept {
    return const_cast<T *>(value);
}
template<typename T>
constexpr T * as_mutable(T * value) noexcept {
    return value;
}
template<typename T>
void as_mutable(T const &&) = delete;

今、あなたはめちゃくちゃにすることさえできずvolatile、使用法は次のようになります

decltype(auto) f() const {
    return something_complicated();
}
decltype(auto) f() {
    return as_mutable(std::as_const(*this).f());
}

const rvalueオーバーロードが削除された "as_mutable"(これが一般的に推奨されます)は、の代わりにがf()返された場合、最後の例が機能しないことに注意してください。TT&
Max Truxa、2018

1
@MaxTruxa:はい、これは良いことです。コンパイルしただけの場合は、ダングリングリファレンスがあります。がをf()返す場合、T2つのオーバーロードは必要ありませんconst。バージョンのみで十分です。
デビッドストーン

確かに、昨日は頭がおかしかったおならをお詫びします。コメントを書いたときに何を考えていたかはわかりません。を返すconst / mutableゲッターのペアを見ていましたshared_ptr。したがって、実際に必要なのは、の代わりにを使用して返すことを除いて、上記とas_mutable_ptrほぼ同じように見えるものです。as_mutableshared_ptrstd::const_pointer_castconst_cast
Max Truxa

1
メソッドが戻る場合、T const*これは(少なくとも私のテストでは)バインドするのT const* const&&ではなく、バインドしT const* const&ます。T const*ポインターを返すメソッドの引数の型として、オーバーロードを追加する必要がありました。
monkey0506

2
@ monkey0506:ポインタと参照をサポートするように回答を更新しました
David Stone

34

Scott Meyersのソリューションは、C ++ 11でtempateヘルパー関数を使用することで改善できると思います。これにより、意図がより明確になり、他の多くのゲッターで再利用できます。

template <typename T>
struct NonConst {typedef T type;};
template <typename T>
struct NonConst<T const> {typedef T type;}; //by value
template <typename T>
struct NonConst<T const&> {typedef T& type;}; //by reference
template <typename T>
struct NonConst<T const*> {typedef T* type;}; //by pointer
template <typename T>
struct NonConst<T const&&> {typedef T&& type;}; //by rvalue-reference

template<typename TConstReturn, class TObj, typename... TArgs>
typename NonConst<TConstReturn>::type likeConstVersion(
   TObj const* obj,
   TConstReturn (TObj::* memFun)(TArgs...) const,
   TArgs&&... args) {
      return const_cast<typename NonConst<TConstReturn>::type>(
         (obj->*memFun)(std::forward<TArgs>(args)...));
}

このヘルパー関数は次のように使用できます。

struct T {
   int arr[100];

   int const& getElement(size_t i) const{
      return arr[i];
   }

   int& getElement(size_t i) {
      return likeConstVersion(this, &T::getElement, i);
   }
};

最初の引数は常にthisポインターです。2番目は、呼び出すメンバー関数へのポインターです。その後、任意の量の追加の引数を渡して、それらを関数に転送できます。可変テンプレートのため、これにはC ++ 11が必要です。


3
それは私たちがstd::remove_bottom_const一緒に行く必要がないのは残念std::remove_constです。
TBBle 2016

まだが埋め込まれているため、このソリューションは好きではありませんconst_cast。あなたは作ることができるgetElementテンプレート自体を、とするタイプの内部の形質を使うmpl::conditionalようにあなたが必要とするタイプ、iteratorSまたはconstiterator必要に応じて、S。実際の問題は、署名のこの部分をテンプレート化できない場合に、メソッドのconstバージョンを生成する方法です。
v.oddou 2016年

2
@ v.oddou:std::remove_const<int const&>is int const &(トップレベルのconst資格を削除)、したがって、NonConst<T>この回答の体操。推定でstd::remove_bottom_constは、最下位のconst資格を削除し、NonConst<T>ここでの処理を正確に実行できます:std::remove_bottom_const<int const&>::type=> int&
TBBle 2016年

4
このソリューションは、getElement過負荷になるとうまく機能しません。その場合、テンプレートパラメータを明示的に指定しないと、関数ポインタを解決できません。どうして?
John

1
C ++ 11の完全転送を使用するには、回答を修正する必要があります。likeConstVersion(TObj const* obj, TConstReturn (TObj::*memFun)(TArgs...) const, TArgs&&... args) { return const_cast<typename NonConst<TConstReturn>::type>((obj->*memFun)(std::forward<TArgs>(args)...)); }完了:gist.github.com/BlueSolei/bca26a8590265492e2f2760d3cefcf83
ShaulF

22

マイヤーズより少し冗長ですが、私はこれを行うかもしれません:

class X {

    private:

    // This method MUST NOT be called except from boilerplate accessors.
    Z &_getZ(size_t index) const {
        return something;
    }

    // boilerplate accessors
    public:
    Z &getZ(size_t index)             { return _getZ(index); }
    const Z &getZ(size_t index) const { return _getZ(index); }
};

プライベートメソッドは、constインスタンスに対して非const Z&を返すという望ましくないプロパティを持っているため、プライベートメソッドです。プライベートメソッドは、外部インターフェイスの不変式を壊す可能性があります(この場合の望ましい不変式は、「constオブジェクトは、それを通じて取得したオブジェクトへの参照を介して取得することはできません」)。

コメントはパターンの一部であることに注意してください-_getZのインターフェイスは、それを呼び出すことは決して有効ではないことを明示しています(明らかにアクセサーを除いて)。結果として、コードが小さくなったり、速くなったりします。メソッドを呼び出すことは、const_castを使用してアクセサの1つを呼び出すことと同等であり、その必要もありません。エラーを明らかにすることが心配な場合(そしてそれが公正な目標です)、_ getZではなくconst_cast_getZと呼びます。

ちなみに、マイヤーズの解決策に感謝します。私はそれに哲学的な異論はありません。ただし、個人的には、ラインノイズのように見えるメソッドよりも、制御された繰り返しのごく一部と、厳密に制御された特定の状況でのみ呼び出す必要のあるプライベートメソッドを好みます。毒を選んでそれを守ってください。

[編集:ケビンは、_getZがgetZと同じようにconstに特化した別のメソッド(たとえば、generateZ)を呼び出したい場合があることを正しく指摘しています。この場合、_getZはconst Z&を参照し、戻る前にconst_castする必要があります。ボイラープレートアクセサーはすべてをポリシングするため、これはまだ安全ですが、安全であることは明白ではありません。さらに、それを行った後、generateZを常にconstを返すように変更した場合、getZを常にconstを返すように変更する必要がありますが、コンパイラーはそのように指示しません。

コンパイラに関する後者の点は、マイヤーズの推奨パターンにも当てはまりますが、非自明なconst_castに関する最初の点はそうではありません。つまり、_getZの戻り値にconst_castが必要になった場合、このパターンではMeyersの値よりも多くの値が失われると思います。また、マイヤーズに比べ不利な点もあるので、その場合は彼に切り替えると思います。一方から他方へのリファクタリングは簡単です。無効なコードとボイラープレートだけが_getZを呼び出すため、クラス内の他の有効なコードには影響しません。]


3
これには、返されるものがXの定数インスタンスに対して定数である可能性があるという問題があります。その場合でも、_getZ(...)にconst_castが必要です。後の開発者が誤用した場合でも、UBにつながる可能性があります。返されるものが「変更可能」である場合、これは良い解決策です。
ケビン

1
ヘッダーファイルやDoxygenなどで、有効な使用に関するBLOCK CAPITAL命令を無視することを選択した場合、プライベート関数(ヘック、パブリック関数も)が後の開発者によって誤用される可能性があります。指示がわかりやすいので問題ないとは思いません。
スティーブジェソップ

13
-1:これは多くの状況で機能しません。関数something内に_getZ()インスタンス変数がある場合はどうなりますか?コンパイラー(または少なくとも一部のコンパイラー)_getZ()は、constなので、内部で参照されるインスタンス変数もconstであると不平を言うでしょう。したがってsomething、const(型const Z&)になり、に変換できませんZ&。私の(確かに多少制限された)経験では、ほとんどのsomething場合、このような場合のインスタンス変数です。
重力

2
@GravityBringer:「何か」にはを含める必要がありconst_castます。これは、複製されたgetter内にあるもののプレースホルダーとしてではなく、constオブジェクトから非constの戻り値を取得するために必要なコードのプレースホルダーになることを目的としています。したがって、「何か」は単なるインスタンス変数ではありません。
Steve Jessop、2011

2
そうですか。ただし、この方法の有用性は本当に低下します。私は反対投票を削除しますが、SOは私をさせません。
重力

22

いい質問といい答え。キャストを使用しない別の解決策があります:

class X {

private:

    std::vector<Z> v;

    template<typename InstanceType>
    static auto get(InstanceType& instance, std::size_t i) -> decltype(instance.get(i)) {
        // massive amounts of code for validating index
        // the instance variable has to be used to access class members
        return instance.v[i];
    }

public:

    const Z& get(std::size_t i) const {
        return get(*this, i);
    }

    Z& get(std::size_t i) {
        return get(*this, i);
    }

};

ただし、静的メンバーが必要であり、そのinstance内部で変数を使用する必要があるという醜さがあります。

このソリューションの考えられる(否定的な)影響をすべて検討することはしませんでした。もしあれば教えてください。


4
さて、ボイラープレートを追加したという単純な事実に進みましょう。どちらかといえば、これは、言語が関数の修飾子と戻り値の型を変更する方法を必要とする理由の例として使用する必要があります auto get(std::size_t i) -> auto(const), auto(&&)。なぜ '&&'?ああ、だから私は言うことができます:auto foo() -> auto(const), auto(&&) = delete;
kfsone 2015

gd1:まさに私が考えていたもの。@kfsoneと私が結論付けたとおりです。
v.oddou 2016年

1
@kfsone構文にはthisキーワードを組み込む必要があります。私が提案するtemplate< typename T > auto myfunction(T this, t args) -> decltype(ident)このキーワードは暗黙オブジェクトのインスタンスを引数として認識され、コンパイラはMyFunctionのは、メンバーまたはであることを認識させることになりますTT呼び出しサイトで自動的に推定されます。これは常にクラスのタイプになりますが、cv資格は無料です。
v.oddou 2016年

2
その解決策には、const_cast戻り値iteratorとを可能にするという利点もあります(1 と比較して)const_iterator
Jarod42 2017

1
実装がcppファイルで移動された場合(そして、複製されない方法は簡単ではないため、おそらくそうなります)、staticクラススコープではなくファイルスコープで実行できます。:-)
Jarod42 2017

8

テンプレートでこれを解決することもできます。このソリューションはやや醜いです(ただし、醜さは.cppファイルに隠されています)が、コンパイラーによるconstnessのチェックが行われ、コードの重複はありません。

.hファイル:

#include <vector>

class Z
{
    // details
};

class X
{
    std::vector<Z> vecZ;

public:
    const std::vector<Z>& GetVector() const { return vecZ; }
    std::vector<Z>& GetVector() { return vecZ; }

    Z& GetZ( size_t index );
    const Z& GetZ( size_t index ) const;
};

.cppファイル:

#include "constnonconst.h"

template< class ParentPtr, class Child >
Child& GetZImpl( ParentPtr parent, size_t index )
{
    // ... massive amounts of code ...

    // Note you may only use methods of X here that are
    // available in both const and non-const varieties.

    Child& ret = parent->GetVector()[index];

    // ... even more code ...

    return ret;
}

Z& X::GetZ( size_t index )
{
    return GetZImpl< X*, Z >( this, index );
}

const Z& X::GetZ( size_t index ) const
{
    return GetZImpl< const X*, const Z >( this, index );
}

私が見ることができる主な欠点は、メソッドのすべての複雑な実装がグローバル関数にあるため、上記のGetVector()のようなパブリックメソッドを使用してXのメンバーを取得する必要があることです(常にconstおよびnon-constバージョン)または、この機能を友達にすることができます。でも友達は嫌いです。

[編集:テスト中に追加されたcstdioの不要なインクルードを削除。]


3
常に、複雑な実装関数を静的メンバーにして、プライベートメンバーにアクセスできます。関数はクラスヘッダーファイルでのみ宣言する必要があり、定義はクラス実装ファイルに存在できます。結局のところ、これはクラス実装の一部です。
CBベイリー

ああそうですね。ヘッダーにテンプレートが表示されるのは好きではありませんが、ここから実装が非常に単純になる可能性がある場合は、おそらくその価値があります。
アンディバラーム

+コードを複製せず、醜いものも使用しないこのソリューションの1 const_cast実際には、実際にはconstでないはずの何かをconstすることなっているものをcanstするために使用される可能性があります)。
HelloGoodbye 2013

現在、これはテンプレートの推定戻り値型で簡略化できます(メンバーの場合、クラスで何を複製する必要があるかを減らすので特に便利です)。
Davis Herring

3

ロジックをプライベートメソッドに移動し、ゲッター内で「参照を取得して返す」ことだけを行うのはどうでしょうか。実際、単純なgetter関数内のstaticキャストとconstキャストについてはかなり混乱しますが、非常にまれな状況を除いて、醜いと思います!


未定義の動作を回避するには、const_castが必要です。Martin Yorkの回答と私のコメントをご覧ください。
ケビン

1
ケビン、マーティンヨークの答え
Peter Nimmo

2

プリプロセッサを使用するのは不正行為ですか?

struct A {

    #define GETTER_CORE_CODE       \
    /* line 1 of getter code */    \
    /* line 2 of getter code */    \
    /* .....etc............. */    \
    /* line n of getter code */       

    // ^ NOTE: line continuation char '\' on all lines but the last

   B& get() {
        GETTER_CORE_CODE
   }

   const B& get() const {
        GETTER_CORE_CODE
   }

   #undef GETTER_CORE_CODE

};

テンプレートやキャストほど豪華ではありませんが、意図が明確になっています(「これら2つの関数は同一である必要があります」)。


1
ただし、バックスラッシュ(複数行マクロの場合は通常)に注意する必要があります。さらに、ほとんどの(すべてではないにしても)エディターで構文の強調表示が失われます。
ルスラン

2

非常に多くの異なる答えがあるにもかかわらず、ほとんどすべてが重いテンプレートマジックに依存しているのは、私には驚くべきことです。テンプレートは強力ですが、マクロは簡潔にそれらを打ち負かすことがあります。多くの場合、両方を組み合わせることで最大の汎用性が実現されます。

FROM_CONST_OVERLOAD()const関数を呼び出すためにnon-const関数に配置できるマクロを作成しました。

使用例:

class MyClass
{
private:
    std::vector<std::string> data = {"str", "x"};

public:
    // Works for references
    const std::string& GetRef(std::size_t index) const
    {
        return data[index];
    }

    std::string& GetRef(std::size_t index)
    {
        return FROM_CONST_OVERLOAD( GetRef(index) );
    }


    // Works for pointers
    const std::string* GetPtr(std::size_t index) const
    {
        return &data[index];
    }

    std::string* GetPtr(std::size_t index)
    {
        return FROM_CONST_OVERLOAD( GetPtr(index) );
    }
};

シンプルで再利用可能な実装:

template <typename T>
T& WithoutConst(const T& ref)
{
    return const_cast<T&>(ref);
}

template <typename T>
T* WithoutConst(const T* ptr)
{
    return const_cast<T*>(ptr);
}

template <typename T>
const T* WithConst(T* ptr)
{
    return ptr;
}

#define FROM_CONST_OVERLOAD(FunctionCall) \
  WithoutConst(WithConst(this)->FunctionCall)

説明:

多くの回答に掲載されているように、非constメンバー関数でコードの重複を回避するための一般的なパターンは次のとおりです。

return const_cast<Result&>( static_cast<const MyClass*>(this)->Method(args) );

この定型文の多くは、型推論を使用して回避できます。最初に、const_castでカプセル化できますWithoutConst()。これは、引数のタイプを推測し、const-qualifierを削除します。第2に、ポインターWithConst()をconst修飾するために同様のアプローチを使用できthisます。これにより、const-overloadedメソッドを呼び出すことができます。

残りは、呼び出しの前に正しい修飾子this->を付け、結果からconstを削除する単純なマクロです。マクロで使用される式は、ほとんどの場合、1:1の転送引数を持つ単純な関数呼び出しであるため、複数の評価などのマクロの欠点は発生しません。省略記号__VA_ARGS__とも使用できますが、コンマ(引数の区切り文字)は括弧内にあります。

このアプローチにはいくつかの利点があります。

  • 最小限の自然な構文-呼び出しをラップするだけ FROM_CONST_OVERLOAD( )
  • 追加のメンバー関数は必要ありません
  • C ++ 98との互換性
  • シンプルな実装、テンプレートメタプログラミングなし、依存関係なし
  • 拡張可能:他のconstの関係は、(のような追加することができconst_iteratorstd::shared_ptr<const T>など)。このWithoutConst()ためには、対応する型のオーバーロードを行います。

制限:このソリューションは、非constオーバーロードがconstオーバーロードとまったく同じように動作するシナリオに最適化されているため、引数を1:1で転送できます。ロジックが異なり、constバージョンをを介して呼び出さない場合はthis->Method(args)、他のアプローチを検討することができます。


2

私のような人のために

  • c ++ 17を使用
  • 最小限のボイラープレート /繰り返しを追加したい
  • マクロの使用を気にしないでください(メタクラスを待っている間...)、

ここに別のテイクがあります:

#include <utility>
#include <type_traits>

template <typename T> struct NonConst;
template <typename T> struct NonConst<T const&> {using type = T&;};
template <typename T> struct NonConst<T const*> {using type = T*;};

#define NON_CONST(func)                                                     \
    template <typename... T> auto func(T&&... a)                            \
        -> typename NonConst<decltype(func(std::forward<T>(a)...))>::type   \
    {                                                                       \
        return const_cast<decltype(func(std::forward<T>(a)...))>(           \
            std::as_const(*this).func(std::forward<T>(a)...));              \
    }

これは基本的に、@ Pait、@ DavidStone、@ sh1からの回答を組み合わせたものです(編集:@cdhowieからの改善)。それが表に追加するのは、単に関数に名前を付けるだけの追加のコード行が1つだけあることです(ただし、引数や戻り値の型の重複はありません)。

class X
{
    const Z& get(size_t index) const { ... }
    NON_CONST(get)
};

注:8.1より前のバージョンでは、gccはこれをコンパイルできません。clang-5以降およびMSVC-19は満足です(コンパイラエクスプローラーによると))。


これはちょうど私にとって正直に働いた。これは素晴らしい答えです、ありがとう!
ショート

異なるタイプの参照をとるオーバーロードがある場合に、正しい戻り値の型を使用していることを確認するために、も引数decltype()で使用std::forwardするべきではありませんget()か?
cdhowie

@cdhowie例を提供できますか?
axxel

@axxel地獄のように考案されたものですが、どうぞNON_CONSTマクロ推論戻り値の型が誤っとconst_cast原因でフォワーディングの欠如に間違った型にsのdecltype(func(a...))タイプ。それらを置き換えることでdecltype(func(std::forward<T>(a)...)) これ解決します。(宣言されたX::getオーバーロードを定義したことがないため、リンカーエラーが発生します。)
cdhowie

1
ありがとう@cdhowie、私は実際には非constのオーバーロードを使用するように例を魅惑:coliru.stacked-crooked.com/a/0cedc7f4e789479e
axxel

1

これは、テンプレートの静的ヘルパー関数のC ++ 17バージョンと、オプションのSFINAEテストです。

#include <type_traits>

#define REQUIRES(...)         class = std::enable_if_t<(__VA_ARGS__)>
#define REQUIRES_CV_OF(A,B)   REQUIRES( std::is_same_v< std::remove_cv_t< A >, B > )

class Foobar {
private:
    int something;

    template<class FOOBAR, REQUIRES_CV_OF(FOOBAR, Foobar)>
    static auto& _getSomething(FOOBAR& self, int index) {
        // big, non-trivial chunk of code...
        return self.something;
    }

public:
    auto& getSomething(int index)       { return _getSomething(*this, index); }
    auto& getSomething(int index) const { return _getSomething(*this, index); }
};

フルバージョン:https : //godbolt.org/z/mMK4r3


1

const / non-const関数のペアを自動的に生成するマクロを思いつきました。

class A
{
    int x;    
  public:
    MAYBE_CONST(
        CV int &GetX() CV {return x;}
        CV int &GetY() CV {return y;}
    )

    //   Equivalent to:
    // int &GetX() {return x;}
    // int &GetY() {return y;}
    // const int &GetX() const {return x;}
    // const int &GetY() const {return y;}
};

実装については、回答の最後を参照してください。

の引数MAYBE_CONSTが重複しています。最初のコピーでCVは、何も置き換えられません。2番目のコピーではに置き換えられconstます。

CVマクロ引数に出現できる回数に制限はありません。

ただし、少し不便があります。CV括弧内にある場合は、この括弧のペアの前にCV_IN

// Doesn't work
MAYBE_CONST( CV int &foo(CV int &); )

// Works, expands to
//         int &foo(      int &);
//   const int &foo(const int &);
MAYBE_CONST( CV int &foo CV_IN(CV int &); )

実装:

#define MAYBE_CONST(...) IMPL_CV_maybe_const( (IMPL_CV_null,__VA_ARGS__)() )
#define CV )(IMPL_CV_identity,
#define CV_IN(...) )(IMPL_CV_p_open,)(IMPL_CV_null,__VA_ARGS__)(IMPL_CV_p_close,)(IMPL_CV_null,

#define IMPL_CV_null(...)
#define IMPL_CV_identity(...) __VA_ARGS__
#define IMPL_CV_p_open(...) (
#define IMPL_CV_p_close(...) )

#define IMPL_CV_maybe_const(seq) IMPL_CV_a seq IMPL_CV_const_a seq

#define IMPL_CV_body(cv, m, ...) m(cv) __VA_ARGS__

#define IMPL_CV_a(...) __VA_OPT__(IMPL_CV_body(,__VA_ARGS__) IMPL_CV_b)
#define IMPL_CV_b(...) __VA_OPT__(IMPL_CV_body(,__VA_ARGS__) IMPL_CV_a)

#define IMPL_CV_const_a(...) __VA_OPT__(IMPL_CV_body(const,__VA_ARGS__) IMPL_CV_const_b)
#define IMPL_CV_const_b(...) __VA_OPT__(IMPL_CV_body(const,__VA_ARGS__) IMPL_CV_const_a)

以下をサポートしないC ++ 20以前の実装CV_IN

#define MAYBE_CONST(...) IMPL_MC( ((__VA_ARGS__)) )
#define CV ))((

#define IMPL_MC(seq) \
    IMPL_MC_end(IMPL_MC_a seq) \
    IMPL_MC_end(IMPL_MC_const_0 seq)

#define IMPL_MC_identity(...) __VA_ARGS__
#define IMPL_MC_end(...) IMPL_MC_end_(__VA_ARGS__)
#define IMPL_MC_end_(...) __VA_ARGS__##_end

#define IMPL_MC_a(elem) IMPL_MC_identity elem IMPL_MC_b
#define IMPL_MC_b(elem) IMPL_MC_identity elem IMPL_MC_a
#define IMPL_MC_a_end
#define IMPL_MC_b_end

#define IMPL_MC_const_0(elem)       IMPL_MC_identity elem IMPL_MC_const_a
#define IMPL_MC_const_a(elem) const IMPL_MC_identity elem IMPL_MC_const_b
#define IMPL_MC_const_b(elem) const IMPL_MC_identity elem IMPL_MC_const_a
#define IMPL_MC_const_a_end
#define IMPL_MC_const_b_end

0

通常、constおよびnon-constバージョンが必要なメンバー関数はゲッターとセッターです。ほとんどの場合、それらは1行なので、コードの重複は問題になりません。


2
それはほとんどの場合本当かもしれません。しかし、例外があります。
ケビン

1
とにかくゲッター、constセッターはあまり意味がありません;)
jwfearn

non-constゲッターは事実上セッターであることを意味しました。:)
Dima

0

私はconst_cast... の使用を正当に正当化した友人のためにこれをしました...それについて知らずに、おそらくこのようなことをしたでしょう(本当にエレガントではありません):

#include <iostream>

class MyClass
{

public:

    int getI()
    {
        std::cout << "non-const getter" << std::endl;
        return privateGetI<MyClass, int>(*this);
    }

    const int getI() const
    {
        std::cout << "const getter" << std::endl;
        return privateGetI<const MyClass, const int>(*this);
    }

private:

    template <class C, typename T>
    static T privateGetI(C c)
    {
        //do my stuff
        return c._i;
    }

    int _i;
};

int main()
{
    const MyClass myConstClass = MyClass();
    myConstClass.getI();

    MyClass myNonConstClass;
    myNonConstClass.getI();

    return 0;
}

0

このようなプライベートヘルパー静的関数テンプレートをお勧めします。

class X
{
    std::vector<Z> vecZ;

    // ReturnType is explicitly 'Z&' or 'const Z&'
    // ThisType is deduced to be 'X' or 'const X'
    template <typename ReturnType, typename ThisType>
    static ReturnType Z_impl(ThisType& self, size_t index)
    {
        // massive amounts of code for validating index
        ReturnType ret = self.vecZ[index];
        // even more code for determining, blah, blah...
        return ret;
    }

public:
    Z& Z(size_t index)
    {
        return Z_impl<Z&>(*this, index);
    }
    const Z& Z(size_t index) const
    {
        return Z_impl<const Z&>(*this, index);
    }
};

-1

このDDJ記事は、const_castを使用する必要のない、テンプレートの特殊化を使用する方法を示しています。このような単純な関数の場合は、実際には必要ありません。

boost :: any_cast(ある時点では、それはもうありません)は、重複を避けるために、constバージョンのconst_castを使用して、非constバージョンを呼び出します。もしあなたがする必要がありますのでかかわらず、非constバージョンにconstのセマンティクスを課すことはできません非常に注意する必要があります。

結局のところ、2つのスニペットが直接重なり合っている限り、コードの重複問題ありません。


DDJの記事はイテレータについて言及しているようです-これは質問には関係ありません。定数は定数データではなく、定数データを指す反復子です。
ケビン

-1

提供されているソリューションjwfearnおよびkevinに追加するには、関数がshared_ptrを返す場合の対応するソリューションを次に示します。

struct C {
  shared_ptr<const char> get() const {
    return c;
  }
  shared_ptr<char> get() {
    return const_pointer_cast<char>(static_cast<const C &>(*this).get());
  }
  shared_ptr<char> c;
};

-1

私が探していたものが見つからなかったので、私は自分でいくつかを転がしました...

これは少し冗長ですが、同じ名前(および戻り値の型)の多数のオーバーロードされたメソッドを一度に処理できるという利点があります。

struct C {
  int x[10];

  int const* getp() const { return x; }
  int const* getp(int i) const { return &x[i]; }
  int const* getp(int* p) const { return &x[*p]; }

  int const& getr() const { return x[0]; }
  int const& getr(int i) const { return x[i]; }
  int const& getr(int* p) const { return x[*p]; }

  template<typename... Ts>
  auto* getp(Ts... args) {
    auto const* p = this;
    return const_cast<int*>(p->getp(args...));
  }

  template<typename... Ts>
  auto& getr(Ts... args) {
    auto const* p = this;
    return const_cast<int&>(p->getr(args...));
  }
};

const名前ごとにメソッドが1 つしかなくても、複製するメソッドがたくさんある場合は、次のようにすることができます。

  template<typename T, typename... Ts>
  auto* pwrap(T const* (C::*f)(Ts...) const, Ts... args) {
    return const_cast<T*>((this->*f)(args...));
  }

  int* getp_i(int i) { return pwrap(&C::getp_i, i); }
  int* getp_p(int* p) { return pwrap(&C::getp_p, p); }

残念ながら、これは名前のオーバーロードを開始するとすぐに機能しなくなります(関数ポインター引数の引数リストはその時点では解決されていないようなので、関数引数に一致するものを見つけることができません)。あなたはそれからあなたの方法をテンプレート化することもできますが:

  template<typename... Ts>
  auto* getp(Ts... args) { return pwrap<int, Ts...>(&C::getp, args...); }

しかし、constメソッドへの参照引数は、テンプレートへの値によると思われる引数に対して一致せず、壊れます。 なぜだかわかりません。これが理由です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.