web-dev-qa-db-ja.com

ユーザー定義クラスをstd :: to_string(able)にする

JavaまたはC#。しかし、自分のクラスを関数std::to_stringの入力として有効にすることは可能/良い/賢明なことですか?例:

class my_class{
public:
std::string give_me_a_string_of_you() const{
    return "I am " + std::to_string(i);
}
int i;
};

void main(){
    my_class my_object;
    std::cout<< std::to_string(my_object);
}

そのようなことがなければ(そして私はそう思う)、それを行うための最良の方法は何ですか?

27
Humam Helfawi

まず、いくつかのADLが以下を支援します。

_namespace notstd {
  namespace adl_helper {
    using std::to_string;

    template<class T>
    std::string as_string( T&& t ) {
      return to_string( std::forward<T>(t) );
    }
  }
  template<class T>
  std::string to_string( T&& t ) {
    return adl_helper::as_string(std::forward<T>(t));
  }
}
_

notstd::to_string(blah)は、スコープ内に_std::to_string_を指定してto_string(blah)のADLルックアップを実行します。

次に、クラスを変更します。

_class my_class{
public:
  friend std::string to_string(my_class const& self) const{
    return "I am " + notstd::to_string(self.i);
  }
  int i;
};
_

notstd::to_string(my_object)は、notstd::to_string(7)と同様に、適切な_to_string_を見つけます。

少し手間をかけると、自動検出および使用される型の.tostring()メソッドもサポートできます。

「最良の」方法とは、未解決の質問です。

いくつかの方法があります。

最初に言うことは、カスタム型の_std::to_string_のオーバーロードは許可されないであるということです。カスタムタイプのstd名前空間では、特殊なテンプレート関数とクラスのみが許可され、_std::to_string_はテンプレート関数ではありません。

つまり、_to_string_を処理する良い方法は、演算子またはswapの実装によく似ています。つまり、引数依存ルックアップが作業を行うことを許可します。

何かを文字列に変換したい場合、次のように書くことができます。

_using std::to_string;
auto s = to_string(x) + " : " + to_string(i);
_

xが名前空間YのタイプXのオブジェクトであり、iがintであると仮定すると、次のように定義できます。

_namespace Y {

  std::string to_string(const X& x);

}
_

これは次のことを意味します:

to_string(x)を呼び出すと、実際にY::to_string(const Y::X&)が選択され、

to_string(i)を呼び出すと、std::to_string(int)が選択されます

さらに進んで、to_stringにoperator <<とほぼ同じことをしたい場合があります。そのため、一方を他方に関して書くことができます。

_namespace Y {

  inline std::ostream& operator<<(std::ostream& os, const X& x) { /* implement here */; return os; }

  inline std::string to_string(const X& x) {
    std::ostringstream ss;
    ss << x;
    return ss.str();
  }
}
_
21
Richard Hodges

これが代替ソリューションです。必ずしもよりエレガントで派手すぎるものはありませんが、代替アプローチです。 to_stringを呼び出すすべてのクラスにToString()関数があることを前提としています。

これは、クラスタイプのオブジェクトでのみ機能し、ToString()関数を呼び出す関数テンプレートです。

    template<typename T, typename = std::enable_if_t<std::is_class<T>::value>>
    std::string to_string(const T& t) {
      return t.ToString();
    }

たぶん、std :: stringでも動作するようにしたいかもしれません。

    template<>
    std::string to_string(const std::string& t) {
      return t;
    }

使用中のコードの例を次に示します。ダミーの名前空間to_sに注意してください。メイン関数にstd :: to_stringを使用すると、テンプレート関数名を踏むため、このように間接的に名前を導入する必要があります。誰かがこれを行う正しい方法を知っているなら、コメントをいただければ幸いです。

    #include <cstring>
    #include <iostream>
    #include <string>
    #include <type_traits>


    union U {
      double d;
      const char* cp;
    };

    struct A {
      enum State { kString, kDouble };
      State state;
      U u;

      void Set(const char* cp) {
        u.cp = cp;
        state = kString;
      }

      std::string ToString() const {
        switch (state) {
          case A::kString : return std::string(u.cp); break;
          case A::kDouble : return std::to_string(u.d); break;
          default : return "Invalid State";
        }
      }
    };

    namespace to_s { using std::to_string; };

    int main() {
      using namespace to_s;
      std::string str = "a Nice string";
      double d = 1.1;
      A a { A::kDouble, {1.2} };

      std::cout << "str: " << to_string(str) << ", d: " << to_string(d) << std::endl;
      std::cout << "a: " << to_string(a) << std::endl;
      a.Set(str.c_str());
      std::cout << "a: " << to_string(a) << std::endl;
      std::memset(&a, 'i', sizeof(a));
      std::cout << "a: " << to_string(a) << std::endl;
    }

ここに私が得たものがあります:

str:ナイス文字列、d:1.100000

a:1.200000

a:素敵な文字列

a:無効な状態

1
Nathan Chappell

独自のto_stringを独自の名前空間で定義できます(例:foo)。

namespace foo {
   std::string to_string(my_class const &obj) {
     return obj.string give_me_a_string_of_you();
   }
}

そしてそれを次のように使用します:

int main(){
    my_class my_object;
    std::cout<< foo::to_string(my_object);
}

残念ながら、標準17.6.4.2.1名前空間std [namespace.std]に準拠しているため、名前空間stdto_stringの独自のバージョンを定義することはできません。 (エンファシス鉱山)

C++プログラムの動作は、特に指定がない限り、名前空間stdまたは名前空間std内の名前空間に宣言または定義を追加する場合、未定義です。プログラムは、標準ライブラリテンプレートのテンプレート特殊化を名前空間stdに追加できます。宣言がユーザー定義型に依存し、特殊化が元のテンプレートの標準ライブラリ要件を満たし、明示的に禁止されていない場合のみ。

1
101010

これにはすでに素晴らしい答えがありますが、代替案を提案したいと思います。フィードバックを歓迎します。

To_string関数名が完全に設定されていない場合は、to_stringでサポートされる型に特化した独自のToStringフリー関数テンプレートを実装できます。

template<class T>
std::string ToString(const T& t)
{
    std::ostringstream stream;
    const uint8_t* pointer = &t;
    for(size_t i=0;i<sizeof(T);++i)
    {
        stream << "0x" << std::hex << pointer[i];
    }
    return stream.str();
}

template<> std::string ToString(const int& t) { return std::to_string(t); }
template<> std::string ToString(const long& t) { return std::to_string(t); }
template<> std::string ToString(const long long& t) { return std::to_string(t); }
template<> std::string ToString(const unsigned& t) { return std::to_string(t); }
template<> std::string ToString(const unsigned long& t) { return std::to_string(t); }
template<> std::string ToString(const unsigned long long& t) { return std::to_string(t); }
template<> std::string ToString(const float& t) { return std::to_string(t); }
template<> std::string ToString(const double& t) { return std::to_string(t); }

ここでのデフォルトの実装は、渡されたクラス参照のメモリ空間の値とともに16進値の文字列を返しますが、特殊化はstd :: to_stringを呼び出します。これにより、すべてのクラスが「文字列化可能」になります。

次に、クラスに独自の専門化を実装する必要があります。

template<> std::string ToString(const my_class& t) { return "I am " + std::to_string(t.i); }
1

おそらく、次のようなoperator<<()をオーバーロードしたいだけです。

std::ostream& operator << ( std::ostream& os, const my_class& rhs ) {
    os << "I am " << rhs.i;
    return os;
}

代わりに:

std::ostream& operator << ( std::ostream& os, const my_class& rhs ) {
    os << rhs.print_a_string();
    return os;
}

それからあなたは単にすることができます:

int main() {
    my_class my_object;
    std::cout << my_object;

    return 0;
}
0
Paul Evans

to_stringの新しいオーバーロードをstd名前空間に追加することはできませんが、名前空間で行うことができます。

namespace my {
   using std::to_string;

   std::string to_string(const my_class& o) {
     return o.give_me_a_string_of_you();
   }
}

その後、すべてのタイプにmy::to_stringを使用できます。

int main()
{
    my_class my_object;

    std::cout << my::to_string(my_object);
    std::cout << my::to_string(5);
}
0
Stas