2

すべての演算子を含まれているオブジェクトに転送する一種のラッパークラスを作成して、含まれているオブジェクトに「なりすます」ことができるようにしようとしています。私が書きたいコードは次のようになります(簡略化):

template<typename T>
class Wrapper
{
private:
    T val;
public:
    Wrapper(T value) : val(value) {}
    auto operator++() -> decltype(++this->val)
    {
        return ++this->val;
    }
};

これは、で正常に機能しますが、intに渡そうとするとstd::string、エラーが発生しますcannot increment value of type 'std::basic_string<char>'

ここでもdeclvalを使用してみましたが、それでもエラーが発生しただけでなく、クラスではないためにエラーが発生したため、事態はさらに悪化std::stringしました。intint

さて、通常の状況では、私がそれを呼び出していないので、この関数はまったく生成されません。ただし、何らかの理由で、decltypeは、まったく生成されていなくても、この関数で処理されています。(decltypeを削除し、return typeをvoidに変更するとstd::string、問題なくコンパイルできます。)

だから私の質問は:これを回避する方法はありますか?SFINAEを使用したクレイジーなトリックかもしれません。または、関数がコードを生成していないため、そもそもこれがコンパイラの不適切な動作である可能性はありますか?

編集:解決策、BЈовићによって提案された解決策からいくらか修正されました:

//Class, supports operator++, get its declared return type
template<typename R, bool IsObj = boost::is_class<R>::value, bool hasOp = boost::has_pre_increment<R>::value> struct OpRet
{
    typedef decltype(++std::declval<R>()) Ret;
};
//Not a class, but supports operator++, return type is R (i.e., ++int returns int)
template<typename R> struct OpRet<R, false, true>
{
    typedef R Ret;
};
//Doesn't support operator++, return type is void
template<typename R> struct OpRet<R, true, false>
{
    typedef void Ret;
};
template<typename R> struct OpRet<R, false, false>
{
    typedef void Ret;
};

template<typename T>
class Wrapper
{
private:
    T val;
public:
    Wrapper(T value) : val(value) {}
    auto operator++() -> typename OpRet<T>::Ret
    {
        return ++val;
    }
};

これは、単純型とクラス型の両方で機能し、クラス型の場合、operator ++の戻り型がRではない状況でも機能します(operator ++ではおそらく非常にまれですが、互換性を最大にするために考慮する価値があります。 )。

4

2 に答える 2

2

これを回避する方法はありますか?

boost::has_pre_incrementSFINAEを使用できます:

#include <string>
#include <boost/type_traits.hpp>


template<typename R,bool hasOp = boost::has_pre_increment<R>::value > struct OpRet
{
  typedef R Ret;
};
template<typename R> struct OpRet<R,false>
{
  typedef void Ret;
};


template<typename T>
class Wrapper
{
private:
    T val;
public:
    Wrapper(T value) : val(value) {}
    auto operator++() -> typename OpRet<T>::Ret
    {
        return ++val;
    }
};

int main()
{
  Wrapper<std::string> a("abc");
  Wrapper<int> b(2);
}

関数がコードを生成していないため、そもそもこれがコンパイラにとって不適切な動作である可能性はありますか?

いいえ。コンパイラは適切な診断を発行します。std::string実際にはプレフィックスインクリメント演算子はありません。[temp.deduct] 7と8はこれについて明確です:

7:

置換は、関数の型とテンプレート パラメーターの宣言で使用されるすべての型と式で発生します。式には、配列境界内または非型テンプレート引数として現れるような定数式だけでなく、sizeof、decltype、および非定数式を許可するその他のコンテキスト内の一般式 (つまり、非定数式) も含まれます。[ 注: 例外仕様での同等の置換は、関数がインスタンス化されるときにのみ行われます。その時点で、置換の結果が無効な型または式になると、プログラムの形式が正しくありません。— エンドノート]

8:

置換の結果が無効な型または式になる場合、型推定は失敗します。...

于 2013-02-13T08:18:19.023 に答える