operator演算子について

[戻る]
松雄 2008/07/28(月) 18:42:03
operator << 演算子を使ったのですが、以下のエラーが出てしまいます。
どなたかご指導お願いいたします。

--エラーの内容--
binary '<<' : no operator defined which takes a right-hand operand of type
'class hoge_timer' (or there is no acceptable conversion)

--クラス定義--
class hoge_timer
{
public:
    hoge_timer (){ ::GetLocalTime((LPSYSTEMTIME) &systime); }

    friend ostringstream& operator << (ostringstream& oss, hoge_timer& rhs)
    {
        oss << setw(4) << setfill('0') << rhs.systime.wYear   << '.'
            << setw(2) << setfill('0') << rhs.systime.wMonth  << '.'
            << setw(2) << setfill('0') << rhs.systime.wDay
            << ' '
            << setw(2) << setfill('0') << rhs.systime.wHour   << ':'
            << setw(2) << setfill('0') << rhs.systime.wMinute << ':'
            << setw(2) << setfill('0') << rhs.systime.wSecond << '.'
            << setw(3) << setfill('0') << rhs.systime.wMilliseconds
            << ' ';

        return oss;
    }

protected:
    SYSTEMTIME    systime;
};

--実行部--
void hoge::func(void)
{
    hoge_timer timer;
    ostringstream oss;

    oss.setf(ios::left, ios::adjustfield);
    oss << setw(14)
        << timer;
}

たいちう 2008/07/28(月) 19:33:42
ostreamなら作ったことがある。
↓じゃ駄目かな?

class hoge_timer {
public:
    // (略)
    friend ostream& operator << (ostream& oss, hoge_timer& rhs) {
        oss << setw(4) << setfill('0') << rhs.systime.wYear   << '.'
            << setw(2) << setfill('0') << rhs.systime.wMonth  << '.'
            << setw(2) << setfill('0') << rhs.systime.wDay
            << ' '
            << setw(2) << setfill('0') << rhs.systime.wHour   << ':'
            << setw(2) << setfill('0') << rhs.systime.wMinute << ':'
            << setw(2) << setfill('0') << rhs.systime.wSecond << '.'
            << setw(3) << setfill('0') << rhs.systime.wMilliseconds
            << ' ';
        return oss;
    }
};

void hoge::func() {
    hoge_timer timer;
    cout << setw(14) << timer;
}

たいちう 2008/07/28(月) 19:39:14
いや、最終的に表示するかは別として文字列がほしいのか。
調べないと分からなくけど今調べる時間がない。
時間が取れる頃までには回答が付いていると思うのでパスします。ごめんなさい。

松雄 2008/07/28(月) 19:39:51
たいちうさん、ありがとうございます。
コンパイルは無事通りました。これから動作確認してみます。
ostringstreamではまずいのでしょうか?

iijima 2008/07/28(月) 20:45:34
>--エラーの内容--
>binary '<<' : no operator defined which takes a right-hand operand of type
>'class hoge_timer' (or there is no acceptable conversion)

"oss << setw(14)"の戻り値がostream&型だからではないでしょうか。

    dynamic_cast< ostringstream& >( oss << setw(14) ) << timer;

とでもするのかな。

επιστημη [HomePage] 2008/07/29(火) 08:58:25
解決してるっポいけど。

> 以下のエラーが出てしまいます。

「どこで」そのエラーが出たか くらいは書いとくのが
渡世の義理ってもんじゃないすかね。

たいちう 2008/07/29(火) 10:06:09
これ以上は私の手には負えないようです。よろしければどなたかヘルプ。

#include <iostream>
#include <sstream>

using namespace std;

class Hoge {
public:
    int x;
    int y;
    Hoge(int x0, int y0) : x(x0), y(y0) {}
    friend ostringstream& operator<<(ostringstream &oss, Hoge &hoge) {
        oss << "oss(" << hoge.x << ", " << hoge.y << ")";
        return oss;
    }
};

// (a)
ostream& operator<<(ostream &os, Hoge &hoge) {
    os << "os (" <<  hoge.x << ", " << hoge.y << ")";
    return os;
}

int main() {
    // (1)
    Hoge hoge(12, 34);
    cout << "(1) : " << hoge << " - " << hoge << endl;

    // (2)
    ostringstream oss;
    oss << hoge << " - " << hoge;
    cout << "(2) : " << oss.str() << endl;
    oss.str("");

    // (3)
    static_cast<ostringstream&>(oss << hoge << " - ") << hoge;
    cout << "(3) : " << oss.str() << endl;

    // quit
    cout << "End." << endl;
    cin.get();
    return 0;
}

// 実行結果(VC++2003 SP6,  WinXP SP3)
(1) : os (12, 34) - os (12, 34)
(2) : oss(12, 34) - os (12, 34)
(3) : oss(12, 34) - oss(12, 34)

松雄さんのエラーは、iijimaさんのご指摘の通り、oss::operator<<()の戻り値が
ostream&型で、
該当する(a)のようなコードが無いからおきているのだと思う。

(2)のような呼び出しで変わっていることが確認できたと思う。
(3)のように書けば、oss用のコードを呼び出せるが、
キャストなしで書くスマートな方法が思いつかない。
Hogeをもっときれいに書けるのかな?

# 私が作るならば、hoge_timerにstring型を返すgetTimeString()とかを書くけどね。

επιστημη [HomePage] 2008/07/29(火) 10:19:55
> ostringstreamではまずいのでしょうか?

んー...
ostream& operator<<(ostream& stream, const hoge_timer& ht)
では困ったことでも起こるのでしょうか?

ostreamに<<できるようにしておけば
ファイルだろが画面だろがストリームだろが、
いろんなもんに<<できて便利だと思うんだけど。

たいちう 2008/07/29(火) 10:51:10
> ostreamに<<できるようにしておけば
> ファイルだろが画面だろがストリームだろが、
> いろんなもんに<<できて便利だと思うんだけど。

ですよね。

// (4)
stringstream ss;
ss << hoge << " - " << hoge;
cout << "(4) : " << ss.str() << endl;

松雄 2008/07/29(火) 11:30:29
[[解決]]
iijimaさん、επιστημηさん、たいちうさん
大変ありがとうございます。そして返事が遅くなり申し訳ありませんでした。
いろいろご指導通りやりました。無事解決できました。

iijima 2008/07/29(火) 12:01:33
>無事解決できました。

どのように解決したのかも書き添えてもらえるとgood.

松雄 2008/07/29(火) 13:14:02
[[解決]]
iijimaさん、おっしゃる通りです。
改めまして。。。

以下を実装したところ、エラーも無くなり動作も完璧でした。

--クラス内--
friend ostream& operator << (ostream& os, hoge_timer& rhs)
{
    os << setw(4) << setfill('0') << rhs.systime.wYear   << DOTCHAR
       << setw(2) << setfill('0') << rhs.systime.wMonth  << DOTCHAR
       << setw(2) << setfill('0') << rhs.systime.wDay
       << SPACECHAR
       << setw(2) << setfill('0') << rhs.systime.wHour   << COLONCHAR
       << setw(2) << setfill('0') << rhs.systime.wMinute << COLONCHAR
       << setw(2) << setfill('0') << rhs.systime.wSecond << DOTCHAR
       << setw(3) << setfill('0') << rhs.systime.wMilliseconds
       << SPACECHAR;

     return os;
}

friend ostringstream& operator << (ostringstream& oss, hoge_timer& rhs)
{
    oss << setw(4) << setfill('0') << rhs.systime.wYear   << DOTCHAR
        << setw(2) << setfill('0') << rhs.systime.wMonth  << DOTCHAR
        << setw(2) << setfill('0') << rhs.systime.wDay
        << SPACECHAR
        << setw(2) << setfill('0') << rhs.systime.wHour   << COLONCHAR
        << setw(2) << setfill('0') << rhs.systime.wMinute << COLONCHAR
        << setw(2) << setfill('0') << rhs.systime.wSecond << DOTCHAR
        << setw(3) << setfill('0') << rhs.systime.wMilliseconds
        << SPACECHAR;
     return oss;
}

void hoge::func() {
    hoge_timer timer;
    cout << setw(14) << timer;    // O.K.
    cout << timer;                // O.K.
}

これで本当に解決です。
皆さん、ご指導有難うございました。

επιστημη [HomePage] 2008/07/29(火) 13:26:22
[[解決]]
ostream, ostringstream の二つの版を実装したのはナゼですか?
ostream だけで十分に思えるのですけど。

tetrapod 2008/07/29(火) 19:26:17
ostringstream は ostream から派生しているので
ostream& で ostringstream や ofstream を保持できるわけで。
# つまり ostringstream& 版は不要、っつーことで

ついでに template 化しておけば wcout にも出力できる、っと
あと注意深く必要なところにだけ const をつけておくと完璧かな

松雄 2008/07/30(水) 08:04:57
[[解決]]
みなさん何度もアドバイスありがとうございます。

επιστημηさん
おっしゃる通りでした。最初、両方ないとコンパイルが通らなかった気がして
そのままでした。うっかり間違って理解するところでした。初心者なもので。。。

tetrapodさん
constとはこんな感じですか!?
friend ostream& operator << (ostream& os, cosnt hoge_timer& rhs)

テンプレートはやった事がないので良く分かりませんでした。

template<class T = ostream>
friend T& operator << (T& lhs, cosnt hoge_timer& rhs)

調べて考えましたがやはり不明でした。

tetrapod 2008/07/30(水) 09:30:11
const は正解!100点
ostream に const をつけては×なのだな。

いきなり template 化は難しいかもしれないな
ostream は basic_ostream<char, char_traits<char> > の typedef である。
basic_ostream<wchar_t, char_traits<wchar_t> > が作れれば wcout に出せる。
というあたりを理解しておかないと不可能だろう。

そーいうわけで unicode 出力対応のため template にするのであれば
ostream を std::basic_ostream<E,T> に置換することになる。

class hoge_timer { ...
    template<typename E, typename T> friend
    std::basic_ostream<E, T>& operator<<(std::basic_ostream<E, T>& os,
        const hoge_timer& x) {
        ...
        return os;
    }
}
ということになるわけだ (operator << をクラス内部で定義していい場合)

クラス外で friend にするためには結構めんどくさい手続きが必要であったりして
難度が1つ上がってしまうのだな

たいちう 2008/07/30(水) 10:00:17
> そーいうわけで unicode 出力対応のため template にするのであれば
> ostream を std::basic_ostream<E,T> に置換することになる。

勉強になるな。私も感謝です。昨日書いたコードを置換してみた。

#include <iostream>
#include <sstream>

using namespace std;

class Hoge {
    int x_;
    int y_;
public:
    Hoge(int x, int y) : x_(x), y_(y) {}

    template<typename E, typename T> friend
        basic_ostream<E, T>& operator<<(basic_ostream<E, T> &os, const Hoge &hoge) {
        os << "(" << hoge.x_ << ", " << hoge.y_ << ")";
        return os;
    }
};

int main() {
    // (1) coutに出力
    Hoge hoge(12, 34);
    cout << "(1) : " << hoge << " - " << hoge << endl;

    // (2) 文字列を取得
    stringstream ss;
    ss << hoge << " - " << hoge;
    cout << "(2) : " << ss.str() << endl;

    // (3) wcoutに出力
    wcout << "(3) : " << hoge << " - " << hoge << endl;

    return 0;
}

松雄 2008/07/30(水) 10:52:44
[[解決]]
tetrapodさん
勉強になります。
>いきなり template 化は難しいかもしれないな
>ostream は basic_ostream<char, char_traits<char> > の typedef である。
>basic_ostream<wchar_t, char_traits<wchar_t> > が作れれば wcout に出せる。
>というあたりを理解しておかないと不可能だろう。
MSDNを早速調べたところちゃんと記載されてました、当然ですが!
なんとなくではありますが、今後の開発でどこまで意識してどのような手順で進めるか
理解できました。ありがとうございます。

たいちうさん
早速、例までご用意くださってありがとうございます。私も早速修正します。

結論
stream系はどうも苦手意識していたせいか理解度が全くありませんでした。
皆さんはどこで知識を得たのですか!?一番の疑問です。
参考サイトなどあれば教えて下さい。

επιστημη [HomePage] 2008/07/30(水) 13:12:09
> 昨日書いたコードを置換してみた

wideなストリームにnarrowな文字列を << してますょ?

> 皆さんはどこで知識を得たのですか

場数を踏む。

tetrapod 2008/07/30(水) 14:07:51
場数には御意

こーいうのって結局興味があるか否かで決まると思うのココロ
・template ってなんだか面白そう、でも難しいかも
・とりあえず例題というかサンプルというか、ないかなー
・ってコンパイラをインストールしたらシステムヘッダがあるぢゃん!
ということで <iostream> とか <vector> とか <algorithm> とかを見てみる。
あたりから始まるのだと思うのよね

んで <algorithm> なんかは中見るとどえらく単純なんでびっくりとか、
で、それを応用している人のページ(επιστημηさんの記事とか)を
みつけてくると、えらく複雑そうなことをあっさり数行で実装していて、
なぜにこんな簡単なコードでこれだけ複雑そうなことが出来るのか悩んだりとか。

興味を持つ→調べてみる→新鮮な驚き→もっと興味を持つ、の正のスパイラル。

boost の中身を読んでバグ発見修正とかそこまで至るとマニアの世界なんだけど

松雄 2008/07/30(水) 14:13:38
επιστημηさん
> 場数を踏む。
私の場合、場数を踏む前の基礎知識が不十分なのが問題です。

申し訳ありませんが、この場を借りましてここで質問させて下さい。

TCHAR szBuffer[256];
_stprintf(szBuffer, "%04hX", -12);

--結果--
"FFF4"

これをostringstreamでやるにはどうすればいいですか?
oss << setw(4) << hex << uppercase << -12 << endl;

--結果--
"FFFFFFF4"

できないのでしょうか?

tetrapod 2008/07/30(水) 15:13:28
-12 とだけ書いたならその型は int になってしまうわけだ
int の -12 は (VC++ では) FFFFFFF4 なので正しい結果が得られている。

言語規格書によれば整数定数の型を long と明示することはできても
short と明示する手段は用意されていないので (片手落ちだね)
cout << setw(4) << hex << short(-12) << endl;
とするのだろうな

printf は可変個引数な関数なので short を渡すことは原理的に出来ないわけで
(暗黙の型変換によって short を渡そうとしても int に変換されている)
"%hX" は、「対応する実引数は int であるが、それを short に変換して表示」
という意味になる。
だから -12 という int 型の値を渡しても short 表示になるのだな

επιστημη [HomePage] 2008/07/30(水) 15:17:46
> cout << setw(4) << hex << short(-12) << endl;
> とするのだろうな

ですねー。

#include <iostream>
#include <iomanip>

using namespace std;

int main() {
  cout << hex << uppercase << setfill('0');
  cout << setw(4) << short( 12) << endl;
  cout << setw(4) << short(-12) << endl;
};

たいちう 2008/07/30(水) 15:20:42
> wideなストリームにnarrowな文字列を << してますょ?

ご指摘ありがとうございます。こっちは直せた。

wcout << L"(3) : " << hoge << L" - " << hoge << endl;


でも、↓これが直せない。

template<typename E, typename T> friend
basic_ostream<E, T>& operator<<(basic_ostream<E, T> &os, const Hoge &hoge) {
    os << "(" << hoge.x_ << ", " << hoge.y_ << ")";
    return os;
}

wideなストリームの場合もあり、問題があるように思うのですが。

επιστημη [HomePage] 2008/07/30(水) 15:48:04
char, wchar_t で部分的な特殊化をしてあげにゃならんかなー
めんどくさげー orz

tetrapod 2008/07/30(水) 15:55:21
そのための char_traits ですよ
static E char_traits::to_char_type(const int_type& ch) を使って
os << T::to_char_type('(') << hoge.x_ ....;

と、ここまで書いて <complex> の中を見てみたら char_traits::to_char_type を
使ってないでやんの (cygwin-gcc-3.4.4)

επιστημη [HomePage] 2008/07/30(水) 15:56:12
いや、文字はそれでいいけど文字列が...

たいちう 2008/07/30(水) 16:14:19
std::endlの定義を調べてみると、basic_ostream::widen(char)というのがある。
これを使ってみた。
変換部分をテンプレート関数にして外に出せば、まぁ我慢して使える範囲かな?

template<typename E, typename T> friend
basic_ostream<E, T>& operator<<(basic_ostream<E, T> &os, const Hoge &hoge) {

    const char s0[3][3] = { "(", ", ", ")" };
    basic_string<E> s1[3];
    for (int i = 0; i < 3; i++) {
        for (int j = 0; s0[i][j]; j++)
            s1[i].push_back(os.widen(s0[i][j]));
    }

    os << s1[0] << hoge.x_ << s1[1] << hoge.y_ << s1[2];

    return os;
}

επιστημη [HomePage] 2008/07/30(水) 16:19:56
"X座標:12, Y座標:-31"と書かせたくなったらどないすんの、と。
# なんにせよめんどっちーんだこれが ^^;

松雄 2008/07/30(水) 16:56:55
[[解決]]
tetrapodさん
επιστημηさん
有難うございます。
oss << setw(4) << hex << uppercase << short(-12) << endl;

で無事解決です。
皆さんテンプレートで盛り上がっていますが、やはりレベルが高すぎて
意味不明です。参考にさせてもらいます。

たいちう 2008/07/30(水) 17:01:29
template<typename E, typename T>
basic_string<E, T> convert(basic_ostream<E, T> &os, const string &str) {
    basic_string<E, T> buf;
    for (unsigned int i = 0; i < str.size(); i++)
        buf.push_back(os.widen(str[i]));
    return buf;
}

class Hoge {
    int x_;
    int y_;
public:
    Hoge(int x, int y) : x_(x), y_(y) {}

    template<typename E, typename T> friend
    basic_ostream<E, T>& operator<<(basic_ostream<E, T> &os, const Hoge &hoge) {
        os << convert(os, "X座標:")
           << hoge.x_ 
           << convert(os, ", Y座標:")
           << hoge.y_;
        return os;
    }
};

これでどうでしょ?
convertは、もっと良い名前にして他のクラスでも使えるし、我慢の範囲内かなーと。

επιστημη [HomePage] 2008/07/30(水) 17:15:06
うーん...たとえば"漢"を二度にわけて(charふたつ)widenしてるわけで、
これでちゃんとwideになるのか、がヒジョーに気になります。
# うまくいったとしても、endianとか絡んでこないかな?

たいちう 2008/07/30(水) 17:30:33
先ほどのコードはマルチバイトでもユニコードでも、失敗しませんでしたが、
そう考えてみると、ちゃんとwideになる方が不思議な気もしますね。

たいちう 2008/07/30(水) 17:36:15
> 皆さんテンプレートで盛り上がっていますが、やはりレベルが高すぎて
> 意味不明です。参考にさせてもらいます。

乗っ取り申し訳ないです。私の程度などは見ての通り、
このスレの他の回答者に全然かなわないのですが、
お詫びを兼ね、かなわないなりに追加。

> 皆さんはどこで知識を得たのですか!?一番の疑問です。

CとC++とMFCとWinAPIの混沌の中で、周囲に聞く人もない中でもがきました。
それ以前にもプログラミングの経験はありましたが、上述の言語等は初めて。

C++だけでも書籍・サイト共に色々参考にしましたが、
お薦めのサイトは「ロベールのC++教室」です。
http://www7b.biglobe.ne.jp/~robe/cpphtml/index.html

書籍では先ほどから机の上に「C++の設計と進化」がのっています。
中盤以降は部分的にしか読めていないのですが。
http://www.amazon.co.jp/dp/4797328541

古いスレッドには追加発言できません。