C ++のテンプレートフレンドリーな文字列から数値へ


48

C ++標準ライブラリには、文字列から数値型に変換する関数があります。

stoi
stol
stoll
stoul
stoull
stof
stod
stold

しかし、テンプレートコードでそれらを使用するのは面倒です。なぜ次のようなテンプレート関数がないのですか?

template<typename T>
T sto(...)

文字列を数値型に変換するには?

それらを持たない技術的な理由は見当たらないが、何かが足りないのかもしれない。それらは、基礎となる名前付き関数を呼び出し、enable_if/ conceptsを使用して非数値型を無効にするように特殊化できます。

標準ライブラリに、文字列を数値型に変換するためのテンプレートに適した代替手段はありますか?



1
@Boiethiosは本当ではありません-その質問からの回答は「なぜ」の背後にある理論的根拠を説明しますが、受け入れられた回答のような実用的な解決策はありません。私は自分の質問を編集して、必要なものをより適切に述べるための代替案を求めるように編集しました
Mircea Ispas

回答:


40

なぜ次のようなテンプレート関数がないのですか?

C ++ 17には、このような汎用の文字列番号関数がありますが、名前が異なります。それらはstd::from_chars、すべての数値型に対してオーバーロードされているで行われました。

ご覧のとおり、最初のオーバーロードは任意の整数型を出力パラメーターとして取得しており、可能であれば値を割り当てます。

次のように使用できます。

template<typename Numeric>
void stuff(std::string_view s) {
    auto value = Numeric{};

    auto [ptr, error] = std::from_chars(s.data(), s.data() + s.size(), value);

    if (error) {
        // error with the conversion
    } else {
        // conversion successful, do stuff with value
    }
}

ご覧のとおり、一般的なコンテキストで機能します。


5
C ++は今、構造を破壊していますか?:o 構造化バインディング宣言
アレクサンダー-

1
もちろん!単純な構造体でも、適切なインターフェースが与えられればクラスでも動作します。
ギヨームラシコット

13

これはテンプレートではなく、ロケールでは機能しませんが、それがショーストッパーでない場合、C ++ 17には既に必要なものが含まれています。 std::from_chars

すべての整数型と浮動小数点型にオーバーロードがあり、インターフェイスは同じですが、最後のパラメーターは整数型と浮動小数点型でそれぞれ異なります(ただし、デフォルトで問題がない場合は、何かを変更します)。これはロケールに対応した関数ではないため、非常に高速です。それは他の文字列から値への変換関数のどれよりも優れており、一般に桁違いです。

Stephan T. Lavavejによる<charconv>(ヘッダーのあるfrom_chars)に関する非常に優れたCPPCONビデオがあり、その使用法とパフォーマンスについては、https//www.youtube.com/watch?v = 4P_kbF0EbZMで見ることができます。


1
@NathanOliver:stoiおよびその友達(質問で述べた変換)もロケールでは機能しないため、これはショートッパーではありません。
ピートベッカー

9

次のような表現では、多くのことは得られません。

int x = sto("1");

テンプレートパラメータの目的のタイプを推測する(簡単な)方法はありません。あなたは書く必要があります

int x = sto<int>("1");

これは、一般的な機能を提供する目的をある程度無効にします。一方、

template<typename T>
void sto(std::string x,T& t);

あなたが実現したように、それは役に立つでしょう。C ++ 17にはがありstd::from_chars、多かれ少なかれそれを正確に実行します(これはテンプレートではなく、オーバーロードのセットであり、文字列ではなく文字へのポインターを受け取りますが、それは細かい部分だけです)。

PS 上記の式で目的の型を推測する簡単な方法はありませんが、方法はあります。私はあなたの質問の核心があなたが求めた署名であったとは思いません、そして以下はそれを実装する良い方法だとは思いませんが、上記をint x = sto("1");コンパイルする方法があることを知り、それを知りたくて興味がありました動作中。

#include <iostream>
#include <string>

struct converter {
    const std::string& x;
    template <typename T> operator T() { return 0;}
};

template <> converter::operator int() { return stoi(x); }
template <> converter::operator double() { return stod(x); }
converter sto(const std::string& x) { return {x}; }

int main() {
    std::string s{"1.23"};
    int x = sto(s);
    double y = sto(s);
    std::cout << x << " " << y;
}

これは意図したとおりに機能しますが、重大な欠点があります。おそらく最も重要なことに、を書くauto x = sto(s);ことができます。つまり、間違って使用するのは簡単です。


ここで暗黙の変換に依存することは良い考えだと思います。autoを無効にしようとするのは問題です。通常、有効なメソッドによってのみ初期化されるクラスにプライベートconst参照を配置することでそれが行われるのを見てきました。先に進む前にコンバーターオブジェクト全体を構築する必要があるため、ここではそれをどのように活用するかわかりません。
うーん

推論されていない型パラメーターにもかかわらず値を見ることができます-質問が言うように、動機は、インスタンス化間で変化する型に変換するテンプレートコード内から使用できるようにすることです。
Toby Speight

基本的な問題はauto x = sto(s)何ですか?この特定の実装converter::xは、範囲外の参照であるために機能しなくなりますが、修正可能です。参照を削除して、std::stringの移動セマンティクスに依存するだけです。
MSalters

@MSaltersはい、それは私が問題があると思ったのは参照でしたが、あなたは正しい、参照を使用する必要はありません。実際に私をさらに妨げているのは、関数のように見えますが、実際の機能はにありますconverter。また、テンプレート変換演算子を使用するのが最善の選択であったかどうか、また修正できるかどうかもわかりません。多分それは私が最初に思ったほど悪くはありません
idclev 463035818

ここでのconst参照に問題はないと思います。私の理解は、コンバータが破壊されるまで、const参照は、(文字列の寿命を維持することであるherbsutter.com/2008/01/01/...を
bremen_matt

5

すべてと互換性のあるソリューション(C ++-98のような古いC ++コンパイラでさえ)は、両方の方法で数値型と文字列型の間で変換するテンプレートであるboost :: lexical_castを使用することです。

例:

short myInt = boost::lexical_cast<short>(*argv);
std::string backToString = boost::lexical_cast<std::string>(myInt);

参照:https : //www.boost.org/doc/libs/1_42_0/libs/conversion/lexical_cast.htm


3

古いC ++バージョンでは、stringstreamはあなたの友人です。私が正しく理解していれば、次のことが興味深いかもしれません。C ++ 11です。

https://wandbox.org/permlink/nUNiUwWWTr7a0NXM

#include <sstream>
#include <string>
#include <iostream>

template<typename T, typename String>
T sto(const String & str) {
    T val;
    std::stringstream ss(str);
    ss >> val;
    return val;
}

template<typename T, typename String>
void sto(const String & str, T & val) {
    std::stringstream ss(str);
    ss >> val;
}

int main() {   
    std::cout << sto<float>("1.1") << ", " << sto<int>(std::string{"2"});

    // An alternative version that infers the type 
    double d;
    sto("3.3", d);
    std::cout << ", " << d;
}

このメソッドはC ++ 11で機能し、かなり一般的です。私の経験では、この方法は堅牢ですが、パフォーマンスは最も高くありません。


はい、これは私が使用したものですが、パフォーマンスは以下の名前付き関数より低い場合があります
Mircea Ispas
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.