5

特定の型に a の左シフト演算子のオーバーロードがあるかどうかを検出するために、機能する型の特性を見つけようとしていますstd::ostream(たとえば、std::coutorと相互運用可能boost::lexical_cast)。boost::has_left_shiftタイプがPODまたはタイプのSTLコンテナである場合を除いて、私は成功しましたstd::string。これは、STL 型または operator<< 関数の特殊化に関係していると思われます。の有効な左シフト演算子で型を一般的に識別する正しい方法は何std::ostreamですか? それが不可能な場合、POD または std::string 型の STL コンテナーで左シフト演算子のオーバーロードを検出する別の方法はありますか?

以下のコードは、私が現在作業しているものを示しており、オーバーロードされた関数が次の行で呼び出されてもboost::has_left_shift検出に失敗する方法を示しています。operator<<このプログラムは、GCC 4.5.1 以降および clang 3.1 でコンパイルおよび動作します。

operator<<明らかな応答を回避するために、テンプレート化された関数を、使用されていたさまざまな型の特定のバージョンに置き換えてみましたが、役に立ちませんでした。また、2 つの型の const-ness と l-value/r-value 指定子のさまざまな組み合わせを試しました (さまざまな調整operator<<により、r-value ostream を使用したオーバーロードを指すコンパイラ メッセージが表示されます)。また、せいぜい と同じ結果が得られる独自の特性を実装しようとしましたboost::has_left_shift

提供できるヘルプを事前に感謝します。また、この動作が発生する理由と解決策の仕組みについての完全な説明を含めることができれば、非常にありがたいです. テンプレートの知識の限界を広げているので、なぜこれがうまくいかないのかを知りたいと思っています。

#include <string>
#include <vector>
#include <iostream>
#include <boost/lexical_cast.hpp>
#include <boost/type_traits/has_left_shift.hpp>

using namespace std;

struct Point {
    int x;
    int y;
    Point(int x, int y) : x(x), y(y) {}
    string getStr() const { return "("+boost::lexical_cast<string>(x)+","+boost::lexical_cast<string>(y)+")"; }
};

ostream& operator<<(ostream& stream, const Point& p)
{
    stream << p.getStr();
    return stream;
}

template <typename T>
ostream& operator<<(ostream& stream, const std::vector<T>& v)
{
    stream << "[";
    for(auto it = v.begin(); it != v.end(); ++it)
    {
        if(it != v.begin())
            stream << ", ";
        stream << *it;
    }
    stream << "]";
    return stream;
}

template <typename T>
void print(const string& name, T& t)
{
    cout << name << " has left shift = " << boost::has_left_shift<ostream , T>::value << endl;
    cout << "t = " << t << endl << endl;
}

int main()
{
    cout << boolalpha;

    int i = 1;
    print("int", i);

    string s = "asdf";
    print("std::string", s);

    Point p(2,3);
    print("Point", p);

    vector<int> vi({1, 2, 3});
    print("std::vector<int>", vi);

    vector<string> vs({"x", "y", "z"});
    print("std::vector<std::string>", vs);

    vector<Point> vp({Point(1,2), Point(3,4), Point(5,6)});
    print("std::vector<Point>", vp);
}
4

3 に答える 3

7

それが機能しない理由は、C ++には、関数呼び出しを解決するための驚くべき(しかし意欲的な)ルールがある場合があるためです。特に、名前検索は、呼び出しが発生する名前空間と引数の名前空間(UDTの場合)で最初に実行されます。名前が一致する関数(または組み込み演算子が一致する関数)が見つかった場合は、それが選択されます(UDTの場合)。または、複数が見つかった場合は、過負荷解決が実行されます)。

引数の名前空間に一致する名前の関数が見つからない場合にのみ、親の名前空間がチェックされます。一致する名前の関数が見つかったが、呼び出しを解決するために実行可能でない場合、または呼び出しがあいまいな場合、コンパイラーは、より適切またはあいまいでない一致を見つけることを期待して、親の名前空間を検索し続けません。呼び出しを解決する方法はないと結論付けます。

このメカニズムは、StephanT.LavavejによるこのプレゼンテーションとHerbSutterによるこの古い記事でうまく説明されています。

あなたの場合、この演算子の存在をチェックする関数はboost名前空間にあります。引数はstd名前空間(ostream、、 )からのものかstringvectorPOD(int)のいずれかです。std名前空間には、実行不可能なオーバーロードがoperator <<存在するため、コンパイラは、オーバーロードが定義されている親(グローバル)名前空間をわざわざ検索しません。boost定義されているかどうかを確認するために名前空間で行われた(シミュレートされた)呼び出しoperator <<は解決できないと単純に結論付けます。

boost::has_left_shiftこれで、コンパイルエラーになる可能性のあるものを置換の失敗に変換するためのSFINAE機構がいくつかある可能性が高く、静的変数に割り当てられますfalsevalue

アップデート:

答えの元の部分は、これが機能しない理由を説明しました。それを回避する方法があるかどうかを見てみましょう。ADLが使用され、std名前空間に実行不可能なオーバーロードが含まれているためoperator <<、呼び出しを解決する試みが事実上ブロックされているため、の実行可能なオーバーロードをoperator <<グローバル名前空間から名前空間に移動したくなる可能性がありstdます。

残念ながら、名前空間を拡張することstd(そして、これは、の新しいオーバーロードを追加する場合に行うことですoperator <<)は、C++標準によって禁止されています。代わりに許可されるのは、名前空間で定義されたテンプレート関数を特殊std化することです(特に明記されていない限り)。ただし、パラメータをで特殊化できるテンプレートがないため、これはここでは役に立ちませんvector<int>。さらに、関数テンプレートを部分的に特殊化することはできません。これにより、作業がはるかに扱いにくくなります。

ただし、最後の可能性が1つあります。それは、呼び出し解決が発生する名前空間にオーバーロードを追加することです。これは、Boost.TypeTraitsの機械のどこかにあります。特に、私たちが関心を持っているのは、呼び出しが行われる名前空間の名前です。

ライブラリの現在のバージョンでは、それはたまたまですが、これがさまざまなBoostバージョン間でboost::detail::has_left_shift_implどれほど移植可能かはわかりません。

ただし、本当に回避策が必要な場合は、その名前空間で演算子を宣言できます。

namespace boost 
{ 
    namespace detail 
    { 
        namespace has_left_shift_impl
        {
            ostream& operator<<(ostream& stream, const Point& p)
            {
                stream << p.getStr();
                return stream;
            }

            template <typename T>
            std::ostream& operator<<(std::ostream& stream, const std::vector<T>& v)
            {
                stream << "[";
                for(auto it = v.begin(); it != v.end(); ++it)
                {
                    if(it != v.begin())
                        stream << ", ";
                    stream << *it;
                }
                stream << "]";
                return stream;
            }
        } 
    } 
}

そして、物事は機能し始めます。

ただし、注意点が1つあります。これはコンパイルされ、GCC 4.7.2で正常に実行され、期待される出力が得られます。ただし、Clang 3.2では、ヘッダーを含めるboost::details::has_left_shift_impl 前にオーバーロードを定義する必要があるようです。has_left_shift.hppこれはバグだと思います。

于 2013-01-28T20:31:28.217 に答える
3

あなたの問題は名前空間と名前検索に関係しています。使用する場合

boost::has_left_shift<ostream , T>::value

コードは名前空間ブーストにあります。そこで、テンプレートパラメータの名前空間で検索しますが、一致するを見つけることができませんoperator<<。関連するタイプがグローバル名前空間に存在しないため、コンパイラはグローバル名前空間を検索しません。

一方、print関数自体はグローバル名前空間にあり、そのすぐ上で宣言された演算子が表示されます。

于 2013-01-28T20:30:03.150 に答える
1

その関数を呼び出すとboost::has_left_shift<>()、引数依存の名前ルックアップを使用して検出された演算子のオーバーロードが考慮されます。オーバーロードはグローバルですが、引数は名前空間からのものです。これが、引数に依存する名前のルックアップでオーバーロードが見つからないstd理由です。boost::has_left_shift<>()

修正するには、オーバーロードをstd名前空間に移動します。

namespace std {

ostream& operator<<(ostream& stream, const Point& p); // definition omitted.

template <typename T>
ostream& operator<<(ostream& stream, const std::vector<T>& v); // definition omitted.

}

標準では名前空間に新しいオーバーロードを定義することが禁止されているstdため、標準クラスまたはサードパーティクラスのカスタムプリンタを独自の名前空間にパッケージ化する方法があります。

#include <vector>
#include <iostream>

namespace not_std {

// Can't directly overload operator<< for standard containers as that may cause ODR violation if
// other translation units overload these as well. Overload operator<< for a wrapper instead.

template<class Sequence>
struct SequenceWrapper
{
    Sequence* c;
    char beg, end;
};

template<class Sequence>
inline SequenceWrapper<Sequence const> as_array(Sequence const& c) {
    return {&c, '[', ']'};
}

template<class Sequence>
std::ostream& operator<<(std::ostream& s, SequenceWrapper<Sequence const> p) {
    s << p.beg;
    bool first = true;
    for(auto const& value : *p.c) {
        if(first)
            first = false;
        else
            s << ',';
        s << value;
    }
    return s << p.end;
}

} // not_std

int main() {
    std::vector<int> v{1,2,3,4};
    std::cout << not_std::as_array(v) << '\n';
}
于 2013-01-28T20:29:48.630 に答える