戻り値の型による関数のオーバーロード?


252

より主流の静的型付き言語が戻り値型による関数/メソッドのオーバーロードをサポートしないのはなぜですか?そんなことは考えられません。パラメータタイプによるオーバーロードをサポートするのと同じくらい便利または合理的です。なぜそれほど人気が​​ないのですか?


回答:


523

他の人が戻り値の型でオーバーロードし、言っていることに反している可能とされ、いくつかの近代的な言語によって行わ。通常の反対は、

int func();
string func();
int main() { func(); }

どちらfunc()が呼び出されているかはわかりません。これはいくつかの方法で解決できます:

  1. そのような状況でどの関数が呼び出されるかを決定するための予測可能な方法を用意してください。
  2. このような状況が発生した場合、それはコンパイル時エラーです。ただし、プログラマーが曖昧さをなくすことができる構文がありますint main() { (string)func(); }
  3. 副作用はありません。副作用がなく、関数の戻り値を使用しない場合、コンパイラーはそもそも関数を呼び出さないようにすることができます。

私が定期的に(ab)返す型によってオーバーロードを使用する2つの言語:PerlHaskell。彼らが何をしているのか説明しよう。

Perlの、の間には根本的な違いがあり、スカラーリストコンテキスト(および他の人が、私たちはふり2があります)。Perlのすべての組み込み関数は、呼び出されたコンテキストに応じてさまざまな処理を実行できます。たとえば、join演算子はリストコンテキスト(結合されるもの)をscalar強制する一方で、演算子はスカラーコンテキストを強制するので、次のように比較してください。

print join " ", localtime(); # printed "58 11 2 14 0 109 3 13 0" for me right now
print scalar localtime(); # printed "Wed Jan 14 02:12:44 2009" for me right now.

Perlのすべての演算子は、スカラーコンテキストとリストコンテキストで何かを実行し、図に示すように、それらは異なる場合があります。(これは、のようなランダムな演算子だけではありませんlocaltime@aリストコンテキストで配列を使用する場合は配列を返し、スカラーコンテキストでは要素の数を返します。たとえばprint @a、要素をprint 0+@a出力し、サイズを出力します。 )さらに、すべての演算子はコンテキストを強制でき+ます。たとえば、加算はスカラーコンテキストを強制します。man perlfuncこれを文書化するすべてのエントリ。たとえば、次のエントリの一部ですglob EXPR

リストコンテキストではEXPR、標準のUnixシェル/bin/cshが行うような値のファイル名展開の(空の可能性がある)リストを返します。スカラーコンテキストでは、globはこのようなファイル名の展開を繰り返し、リストがすべて使用されるとundefを返します。

さて、リストとスカラーコンテキストの関係は何ですか?まあ、man perlfunc言う

次の重要なルールを覚えておいてください。リストコンテキスト内の式の動作をスカラーコンテキスト内の式の動作に関連付ける、またはその逆を行うルールはありません。まったく異なる2つのことを行う可能性があります。各演算子と関数は、スカラーコンテキストで返すのに最適な値の種類を決定します。一部の演算子は、リストのコンテキストで返されるはずだったリストの長さを返します。一部の演算子は、リストの最初の値を返します。一部の演算子は、リストの最後の値を返します。一部の演算子は、成功した操作の数を返します。一般に、一貫性が必要でない限り、必要な処理を実行します。

したがって、単一の関数を持つことは単純な問題ではなく、最後に単純な変換を行います。実際、私はlocaltimeその理由で例を選択しました。

この動作をするのはビルトインだけではありません。すべてのユーザーがを使用してこのような関数を定義wantarrayできます。これにより、リスト、スカラー、およびvoidコンテキストを区別できます。したがって、たとえば、無効なコンテキストで呼び出されている場合は、何もしないことを決定できます。

これで、戻り値による真のオーバーロードではないことに不満を言うかもしれません。これは、呼び出されたコンテキストに通知され、その情報に基づいて機能する関数が1つしかないためです。ただし、これは明らかに同等です(そして、Perlが文字通り通常のオーバーロードを許可しない方法に似ていますが、関数はその引数を調べることができます)。さらに、この応答の冒頭で述べたあいまいな状況をうまく解決します。Perlは、呼び出すメソッドがわからないことについて文句を言いません。呼び出すだけです。関数が呼び出されたコンテキストを特定するだけで、常に可能です。

sub func {
    if( not defined wantarray ) {
        print "void\n";
    } elsif( wantarray ) {
        print "list\n";
    } else {
        print "scalar\n";
    }
}

func(); # prints "void"
() = func(); # prints "list"
0+func(); # prints "scalar"

(注:関数を意味する場合、Perlオペレーターと言うこともあります。これは、この説明では重要ではありません。)

Haskellは他のアプローチ、つまり副作用がないようにします。また、強力な型システムがあるため、次のようなコードを記述できます。

main = do n <- readLn
          print (sqrt n) -- note that this is aligned below the n, if you care to run this

このコードは、標準入力から浮動小数点数を読み取り、その平方根を出力します。しかし、これについて驚くべきことは何ですか?さて、タイプはreadLnですreadLn :: Read a => IO a。これが意味することは、可能な型Read(正式には、Read型クラスのインスタンスであるすべての型)readLnがそれを読み取ることができるということです。Haskellはどうやって浮動小数点数を読みたいと思ったのですか?まあ、タイプはsqrtisですsqrt :: Floating a => a -> a。これは本質的に、sqrt浮動小数点数のみを入力として受け入れることができることを意味します。そのため、Haskellは私が欲しかったものを推測しました。

Haskellが私が欲しいものを推測できない場合はどうなりますか?まあ、いくつかの可能性があります。戻り値をまったく使用しない場合、Haskellはそもそも関数を呼び出しません。私はしかし、もしやる戻り値を使用し、その後、Haskellのは、それが型を推論することはできませんと文句を言うだろう:

main = do n <- readLn
          print n
-- this program results in a compile-time error "Unresolved top-level overloading"

必要なタイプを指定することで、あいまいさを解決できます。

main = do n <- readLn
          print (n::Int)
-- this compiles (and does what I want)

とにかく、この全体の議論が意味することは、戻り値によるオーバーロードが可能であり、実行され、それがあなたの質問の一部に答えることです。

あなたの質問の他の部分は、より多くの言語がそれをしない理由です。他の人に答えさせましょう。ただし、いくつかのコメント:主な理由は、混乱の可能性が引数の型によるオーバーロードよりもここで本当に大きいためです。個々の言語からの論拠も見ることができます:

エイダ:「最も単純なオーバーロード解決ルールは、すべて-可能な限り広いコンテキストからのすべての情報-を使用してオーバーロードされた参照を解決することです。このルールは単純かもしれませんが、役に立ちません。人間の読者が必要です任意の大きなテキストをスキャンし、任意の複雑な推論を行う(上記(g)など)。私たちは、より良いルールは、人間の読者またはコンパイラが実行する必要のあるタスクを明示的にするルールであり、それによりこのタスクを実行する人間の読者にとって可能な限り自然なことです。」

C ++(Bjarne Stroustrupの「C ++プログラミング言語」のサブセクション7.4.1):「オーバーロードの解決では戻り値の型は考慮されません。理由は、個々の演算子または関数呼び出しの解決をコンテキストに依存しないようにするためです。考慮してください。

float sqrt(float);
double sqrt(double);

void f(double da, float fla)
{
    float fl = sqrt(da);     // call sqrt(double)
    double d = sqrt(da); // call sqrt(double)
    fl = sqrt(fla);            // call sqrt(float)
    d = sqrt(fla);             // call sqrt(float)
}

戻り値の型を考慮に入れると、呼び出しを個別に調べて、呼び出されたsqrt()関数を判別することはできなくなります。」(比較のため、Haskellでは暗黙的な変換は行われないことに注意してください。)

Java(Java言語仕様9.4.1):「継承されたメソッドの1つは、他のすべての継承されたメソッドに対してreturn-type-substitutableでなければなりません。そうでない場合、コンパイル時エラーが発生します。」(はい、これが理論的根拠を与えていないことを知っています。その論理的根拠はGoslingによって「Javaプログラミング言語」で与えられていると確信しています。たぶん誰かがコピーを持っていますか?それは本質的に「驚きが最も少ない原理」でしょう。 )しかし、Javaに関するおもしろい事実:JVM 戻り値によってオーバーロードを許可します!これは、たとえばScalaで使用されており、Java介して直接アクセスしたり、内部で遊んだりすることもできます。

PS。最後に、C ++では、トリックを使用して戻り値によってオーバーロードすることが実際に可能です。証人:

struct func {
    operator string() { return "1";}
    operator int() { return 2; }
};

int main( ) {
    int x    = func(); // calls int version
    string y = func(); // calls string version
    double d = func(); // calls int version
    cout << func() << endl; // calls int version
    func(); // calls neither
}

すばらしい投稿ですが、読みが何かを明確にしたい場合があります(文字列->何か)。
Thomas Eding、2010年

C ++では、constの戻り値ではなくconstでオーバーロードすることもできます。stackoverflow.com/questions/251159/...
ゼオン

3
強制演算子のオーバーロードに関する最後のトリックでは、「cout」行が時々機能しますが、コードに加えたほとんどすべての変更により、「 'operator <<'のあいまいなオーバーロード」が発生します。
スティーブ

1
私が好むアプローチは、1つのオーバーロードを「優先」としてマークすることを要求することです。コンパイラーは、優先オーバーロードのみを使用してバインドすることから始めて、非優先オーバーロードが改善になるかどうかを判断します。特に、型が双方向変換FooBarサポートし、メソッドがFoo内部的にtypeを使用するが、typeを返すとしますBar。そのようなメソッドが、結果をただちにtypeに強制するコードによって呼び出される場合FooBar戻り値の型を使用しても機能する可能性Fooがありますが、どちらかをお勧めします。ところで、私もそれをする方法を見たいです...
スーパーキャット

...メソッドは、コンストラクトなどで使用するタイプを指定することができvar someVar = someMethod();ます(または、そのような方法で戻り値を使用しないように指定することもできます)。例えば、流暢インタフェースは可変および不変のバージョンを持っていることから恩恵を受けるかもしれない道具という種類の家族は、そうvar thing2 = thing1.WithX(3).WithY(5).WithZ(9);だろうWithX(3)コピーthing1可変オブジェクト、のmutate X、および可変オブジェクトそのリターンにします。WithY(5)Yを変更して同じオブジェクトを返します。同様に `WithZ(9)。次に、割り当ては不変の型に変換されます。
supercat

37

関数が戻り値の型によってオーバーロードされ、これらの2つのオーバーロードがあった場合

int func();
string func();

このような呼び出しを見て、コンパイラがこれら2つの関数のどちらを呼び出すかを判断する方法はありません。

void main() 
{
    func();
}

このため、言語設計者はしばしば戻り値のオーバーロードを許可しません。

一部の言語は、(MSILなど)、しかし、ない戻り値の型でのオーバーロードが可能。もちろん、これらも上記の困難に直面していますが、回避策があり、そのドキュメントを参照する必要があります。


4
マイナーな問題(あなたの答えが非常に明確で理解できる理論的根拠を与える):それは方法がないということではありません。それはほとんどの人が望むよりも方法が不器用で苦痛になるというだけです。たとえば、C ++では、オーバーロードはおそらく醜いキャスト構文を使用して解決できたでしょう。
マイケル・バー、

2
@JörgW Mittag:関数の機能がわかりません。彼らは簡単に異なる副作用を持つことができます。
A.レックス、

2
@Jörg-ほとんどの主流のプログラミング言語(C / C ++、C#、Javaなど)では、関数には一般に副作用があります。実際、副作用のある関数は、少なくともそれがない関数と同じくらい一般的だと思います。
マイケルバー

6
ここで遅くジャンプしますが、一部のコンテキストでは、「関数」は(本質的に)「副作用のないメソッド」の狭い定義を持っています。より口語的には、「関数」は「メソッド」または「サブルーチン」と同じ意味で使用されることがよくあります。Jorgは、あなたの視点に応じて、厳格または
慎重

3
でも後に、ビューのいくつかの点が厳しいか知識をひけらかす以外の形容詞を使用する場合がありますでジャンピング
パトリック・マクドナルド

27

そのような言語では、次のことをどのように解決しますか?

f(g(x))

場合はf過負荷を持っていたvoid f(int)void f(string)し、g過負荷を持っていたint g(int)string g(int)?なんらかの曖昧さ回避手段が必要になります。

これが必要になる可能性がある状況では、関数の新しい名前を選択する方が適切だと思います。


2
通常のオーバーロードは、あいまいさを引き起こす可能性もあります。これらは通常、必要なキャストの数を数えることで解決されると思いますが、これが常に機能するとは限りません。
ジェイコンロッド

1
はい、標準の変換は完全一致、昇格、変換にランク付けされます。void f(int); void f(long); f( 'a'); f(int)を呼び出します。これは単なるプロモーションであるため、longへの変換は変換です。void f(float); void f(short); f(10); 両方の変換が必要です:呼び出しがあいまいです。
ヨハネスシャウブ-litb 2009年

言語の遅延評価がある場合、これはそれほど問題ではありません。
jdd

賛成投票、パラメーター型のオーバーロードと戻り値の型のオーバーロードの相互作用は、Rexの投稿では扱われていません。非常に良い点です。
ジョセフガービン

1
私が言語を設計していた場合、私のルールは、オーバーロードされた関数の場合、各パラメーターシグネチャがデフォルトとして指定された1つの戻り値の型を持つ必要があるということです。コンパイラーは、すべての関数呼び出しがデフォルトのタイプを使用すると想定することから始めます。ただし、これが完了すると、関数の戻り値がすぐに別の値にキャストまたは強制変換されるすべての状況で、コンパイラーは、パラメーターシグネチャは同じであるが、戻り値の型がより一致する(またはvoidである)オーバーロードをチェックします。 。そのようなオーバーロードに対しては、おそらく "override-one--override-all"ルールも課すでしょう。
スーパーキャット2012年

19

非常に類似した別の質問から C ++固有の回答を盗む(重複?):


関数の戻り値の型は、単純にStroustrup(他のC ++アーキテクトからの入力で想定)がオーバーロードの解決を「コンテキストに依存しない」ようにしたかったため、オーバーロードの解決では機能しません。「C ++プログラミング言語、第3版」の7.4.1-「オーバーロードと戻り値の型」を参照してください。

その理由は、個々のオペレーターまたは関数呼び出しの解決をコンテキストに依存しないようにするためです。

彼らは、オーバーロードがどのように呼び出されたかにのみ基づいており、結果がどのように使用されたか(まったく使用されなかった場合)に基づいていないことを望んでいました。実際、多くの関数は結果を使用せずに呼び出されます。そうでない場合、結果はより大きな式の一部として使用されます。私が彼らがこれを決定したときに私が確信した1つの要因は、戻り値の型が解決の一部である場合、複雑なルールで解決する必要がある、またはコンパイラーをスローしなければならないオーバーロードされた関数への多くの呼び出しがあることでした呼び出しがあいまいであるというエラー。

そして、主は知っています、C ++オーバーロードの解決は、現状では十分に複雑です...


5

haskellでは、関数のオーバーロードがなくても可能です。Haskellは型クラスを使用します。プログラムでは、次のことがわかります。

class Example a where
    example :: Integer -> a

instance Example Integer where  -- example is now implemented for Integer
    example :: Integer -> Integer
    example i = i * 10

関数のオーバーロード自体はそれほど一般的ではありません。私がこれで見たほとんどの言語はC ++、おそらくjavaやC#です。すべての動的言語では、以下の省略形です。

define example:i
  ↑i type route:
    Integer = [↑i & 0xff]
    String = [↑i upper]


def example(i):
    if isinstance(i, int):
        return i & 0xff
    elif isinstance(i, str):
        return i.upper()

したがって、そこにはあまり意味がありません。ほとんどの人は、言語があなたがそれを使用する場所ごとに1行ずつドロップするのに役立つかどうかに興味がありません。

パターンマッチングは、関数のオーバーロードにいくぶん似ており、時々同様に機能すると思います。ただし、いくつかのプログラムにのみ役立ち、ほとんどの言語で実装するのが難しいため、一般的ではありません。

言語に実装するための、実装がより簡単な機能は他にも無限にあります。

  • 動的型付け
  • リスト、辞書、Unicode文字列の内部サポート
  • 最適化(JIT、型推論、コンパイル)
  • 統合された導入ツール
  • ライブラリサポート
  • コミュニティのサポートと集まる場所
  • 豊富な標準ライブラリ
  • 良い構文
  • eval印刷ループを読み取る
  • リフレクティブプログラミングのサポート

3
Haskellはオーバーロードしています。型クラスは、オーバーロードされた関数を定義するために使用される言語機能です。
Lii

2

良い答え!A.Rexの答えは特に非常に詳細で有益です。彼が指摘するように、C ++ コンパイル時にユーザー指定の型変換演算子を考慮しますlhs = func(); (funcは実際には構造体の名前です)。。私の回避策は少し異なります-良くはありませんが、違うだけです(同じ基本的な考え方に基づいていますが)。

私が持っていたのに対したかった書き込みに...

template <typename T> inline T func() { abort(); return T(); }

template <> inline int func()
{ <<special code for int>> }

template <> inline double func()
{ <<special code for double>> }

.. etc, then ..

int x = func(); // ambiguous!
int x = func<int>(); // *also* ambiguous!?  you're just being difficult, g++!

パラメータ化された構造体(T =戻り値の型)を使用するソリューションができました。

template <typename T>
struct func
{
    operator T()
    { abort(); return T(); } 
};

// explicit specializations for supported types
// (any code that includes this header can add more!)

template <> inline
func<int>::operator int()
{ <<special code for int>> }

template <> inline
func<double>::operator double()
{ <<special code for double>> }

.. etc, then ..

int x = func<int>(); // this is OK!
double d = func<double>(); // also OK :)

このソリューションの利点は、これらのテンプレート定義を含むすべてのコードが、より多くの型に特化を追加できることです。また、必要に応じて構造体を部分的に特殊化することもできます。たとえば、ポインタ型の特別な処理が必要な場合:

template <typename T>
struct func<T*>
{
    operator T*()
    { <<special handling for T*>> } 
};

ネガとして、あなたはint x = func();私の解決策では書くことができません。書いてくださいint x = func<int>();。型変換演算子を調べて戻り値の型をコンパイラに要求するのではなく、戻り値の型を明示する必要があります。「私の」ソリューションとA.Rexのソリューションはどちらも、このC ++のジレンマに取り組むための方法の最前線にあると言えます:)


1

戻り値の種類が異なるメソッドをオーバーロードする場合は、デフォルト値のダミーパラメータを追加して、オーバーロードの実行を許可します。ただし、パラメータタイプが異なるようにして、オーバーロードロジックが次に機能することを忘れないでください。

type    
    myclass = class
    public
      function Funct1(dummy: string = EmptyStr): String; overload;
      function Funct1(dummy: Integer = -1): Integer; overload;
    end;

このように使います

procedure tester;
var yourobject : myclass;
  iValue: integer;
  sValue: string;
begin
  yourobject:= myclass.create;
  iValue:= yourobject.Funct1(); //this will call the func with integer result
  sValue:= yourobject.Funct1(); //this will call the func with string result
end;

それはひどい考えです。ダミーパラメータを導入しないでください。これは大きなコードの匂いです。代わりに、別の名前を選択するか、同じように動作できる、または識別された共用体などである戻り値のタイプを選択してください。
アベル

@Abelあなたが提案しているのは、実際にはひどい考えです。アイデア全体がこのダミーパラメータに関するものであり、開発者がこのパラメータがダミーであり、無視する必要があることを明確にするために、そのように名前が付けられているためです。デフォルト値のダミーパラメータが多くのライブラリ、delphiのVCL、および多くのIDEで使用されていることを知りません。たとえば、delphiではSafeLoadLibraryのsysutilsユニットで確認できます...
ZORRO_BLANCO

確かに、マップまたは折りたたみ操作のラムダのように、またはインターフェイスを実装するときに、ダミーのパラメーターが役立つシナリオがあります。しかし、過負荷を作成するためだけに、いいえ、私は同意しません。プログラマーがいなくても問題はありません。
アベル

0

すでに示したように、戻り値の型のみが異なる関数のあいまいな呼び出しは、あいまいさをもたらします。あいまいさは欠陥のあるコードを引き起こします。欠陥のあるコードは避けなければなりません。

あいまいさの試みによって引き起こされた複雑さは、これが良いハックではないことを示しています。知的なエクササイズとは別に、参照パラメーターを持つ手順を使用してみませんか。

procedure(reference string){};
procedure(reference int){};
string blah;
procedure(blah)

「戻り値」をすぐに再利用するのは簡単ではないためです。対照的に、あなたは、単一のライン上の各呼び出しを行う必要があるだろうdoing(thisVery(deeplyNested(), andOften(butNotAlways()), notReally()), goodCode());
Adowrath

0

このオーバーロード機能は、少し異なる方法で見ると、管理が難しくありません。以下を検討してください、

public Integer | String f(int choice){
if(choice==1){
return new string();
}else{
return new Integer();
}}

言語がオーバーロードを返した場合、パラメーターのオーバーロードは許可されますが、重複は許可されません。これは次の問題を解決します:

main (){
f(x)
}

選択するf(int choice)が1つしかないためです。


0

.NETでは、1つのパラメーターを使用して一般的な結果から必要な出力を示し、期待どおりの結果に変換することがあります。

C#

public enum FooReturnType{
        IntType,
        StringType,
        WeaType
    }

    class Wea { 
        public override string ToString()
        {
            return "Wea class";
        }
    }

    public static object Foo(FooReturnType type){
        object result = null;
        if (type == FooReturnType.IntType) 
        {
            /*Int related actions*/
            result = 1;
        }
        else if (type == FooReturnType.StringType)
        {
            /*String related actions*/
            result = "Some important text";
        }
        else if (type == FooReturnType.WeaType)
        {
            /*Wea related actions*/
            result = new Wea();
        }
        return result;
    }

    static void Main(string[] args)
    {
        Console.WriteLine("Expecting Int from Foo: " + Foo(FooReturnType.IntType));
        Console.WriteLine("Expecting String from Foo: " + Foo(FooReturnType.StringType));
        Console.WriteLine("Expecting Wea from Foo: " + Foo(FooReturnType.WeaType));
        Console.Read();
    }

この例も役立つかもしれません:

C ++

    #include <iostream>

enum class FooReturnType{ //Only C++11
    IntType,
    StringType,
    WeaType
}_FooReturnType;

class Wea{
public:
    const char* ToString(){
        return "Wea class";
    }
};

void* Foo(FooReturnType type){
    void* result = 0;
    if (type == FooReturnType::IntType) //Only C++11
    {
        /*Int related actions*/
        result = (void*)1;
    }
    else if (type == FooReturnType::StringType) //Only C++11
    {
        /*String related actions*/
        result = (void*)"Some important text";
    }
    else if (type == FooReturnType::WeaType) //Only C++11
    {
        /*Wea related actions*/
        result = (void*)new Wea();
    }
    return result;
}

int main(int argc, char* argv[])
{
    int intReturn = (int)Foo(FooReturnType::IntType);
    const char* stringReturn = (const char*)Foo(FooReturnType::StringType);
    Wea *someWea = static_cast<Wea*>(Foo(FooReturnType::WeaType));
    std::cout << "Expecting Int from Foo: " << intReturn << std::endl;
    std::cout << "Expecting String from Foo: " << stringReturn << std::endl;
    std::cout << "Expecting Wea from Foo: " << someWea->ToString() << std::endl;
    delete someWea; // Don't leak oil!
    return 0;
}

1
これは一種のハックであり、ユーザーが結果を適切にキャストしない場合、または開発者が戻り値の型と列挙型を適切に一致させない場合、ランタイムエラーが発生する可能性があります。この回答の
sleblanc

0

記録では、Octaveは、戻り要素がスカラーか配列かによって異なる結果を許可します。

x = min ([1, 3, 0, 2, 0])
   ⇒  x = 0

[x, ix] = min ([1, 3, 0, 2, 0])
   ⇒  x = 0
      ix = 3 (item index)

Cfはまた、特異値分解です。


0

これはC ++では少し異なります。戻り値の型によって直接オーバーロードされると見なされるかどうかはわかりません。方法で機能するのは、テンプレートの特殊化の詳細です。

util.h

#ifndef UTIL_H
#define UTIL_H

#include <string>
#include <sstream>
#include <algorithm>

class util {
public: 
    static int      convertToInt( const std::string& str );
    static unsigned convertToUnsigned( const std::string& str );
    static float    convertToFloat( const std::string& str );
    static double   convertToDouble( const std::string& str );

private:
    util();
    util( const util& c );
    util& operator=( const util& c );

    template<typename T>
    static bool stringToValue( const std::string& str, T* pVal, unsigned numValues );

    template<typename T>
    static T getValue( const std::string& str, std::size_t& remainder );
};

#include "util.inl"

#endif UTIL_H

util.inl

template<typename T>
static bool util::stringToValue( const std::string& str, T* pValue, unsigned numValues ) {
    int numCommas = std::count(str.begin(), str.end(), ',');
    if (numCommas != numValues - 1) {
        return false;
    }

    std::size_t remainder;
    pValue[0] = getValue<T>(str, remainder);

    if (numValues == 1) {
        if (str.size() != remainder) {
            return false;
        }
    }
    else {
        std::size_t offset = remainder;
        if (str.at(offset) != ',') {
            return false;
        }

        unsigned lastIdx = numValues - 1;
        for (unsigned u = 1; u < numValues; ++u) {
            pValue[u] = getValue<T>(str.substr(++offset), remainder);
            offset += remainder;
            if ((u < lastIdx && str.at(offset) != ',') ||
                (u == lastIdx && offset != str.size()))
            {
                return false;
            }
        }
    }
    return true;    
}

util.cpp

#include "util.h"

template<>
int util::getValue( const std::string& str, std::size_t& remainder ) {
    return std::stoi( str, &remainder );
} 

template<>
unsigned util::getValue( const std::string& str, std::size_t& remainder ) {
    return std::stoul( str, &remainder );
}

template<>
float util::getValue( const std::string& str, std::size_t& remainder ) {
    return std::stof( str, &remainder );
}     

template<>   
double util::getValue( const std::string& str, std::size_t& remainder ) {
    return std::stod( str, &remainder );
}

int util::convertToInt( const std::string& str ) {
    int i = 0;
    if ( !stringToValue( str, &i, 1 ) ) {
        std::ostringstream strStream;
        strStream << __FUNCTION__ << " Bad conversion of [" << str << "] to int";
        throw strStream.str();
    }
    return i;
}

unsigned util::convertToUnsigned( const std::string& str ) {
    unsigned u = 0;
    if ( !stringToValue( str, &u, 1 ) ) {
        std::ostringstream strStream;
        strStream << __FUNCTION__ << " Bad conversion of [" << str << "] to unsigned";
        throw strStream.str();
    }
    return u;
}     

float util::convertToFloat(const std::string& str) {
    float f = 0;
    if (!stringToValue(str, &f, 1)) {
        std::ostringstream strStream;
        strStream << __FUNCTION__ << " Bad conversion of [" << str << "] to float";
        throw strStream.str();
    }
    return f;
}

double util::convertToDouble(const std::string& str) {
    float d = 0;
    if (!stringToValue(str, &d, 1)) {
        std::ostringstream strStream;
        strStream << __FUNCTION__ << " Bad conversion of [" << str << "] to double";
        throw strStream.str();
    }
    return d;
}

この例では、戻り値の型による関数のオーバーロードの解決を正確に使用していませんが、このc ++非オブジェクトクラスは、テンプレートの特殊化を使用して、プライベート静的メソッドで戻り値の型による関数のオーバーロードの解決をシミュレートしています。

それぞれの convertToType関数は関数テンプレートstringToValue()を呼び出しており、この関数テンプレートの実装の詳細またはアルゴリズムを見ると、関数が呼び出してgetValue<T>( param, param )おり、型Tを返し、それをにT*渡されるに格納しています。stringToValue()パラメーターの1つとして関数テンプレートにいます。 。

このようなもの以外; C ++には、戻り値の型によって関数のオーバーロードを解決するメカニズムは実際にはありません。知らないうちに、戻り値の型による解決をシミュレートできる他の構造またはメカニズムが存在する場合があります。


-1

これは現代のC ++定義のギャップだと思います…なぜですか?

int func();
double func();

// example 1. → defined
int i = func();

// example 2. → defined
double d = func();

// example 3. → NOT defined. error
void main() 
{
    func();
}

C ++コンパイラが例「3」でエラーをスローできず、例「1 + 2」のコードを受け入れることができないのはなぜですか?


はい、それは彼らがC#(そしておそらくC ++)のために当時検討していたことです。しかし、コードは取るに足らないものですが、クラス階層、仮想メソッド、抽象、インターフェイス、その他のオーバーロード、時には多重継承を追加すると、解決すべきメソッドを決定するのが非常に複雑になります。その道をたどらないのはデザイナーの選択ですが、他の言語はさまざまな成功レベルで異なる方法で決定しています。
Abel

-2

ほとんどの静的言語もジェネリックをサポートするようになり、問題が解決されます。前に述べたように、パラメーターdiffがないと、どれを呼び出すかを知る方法がありません。したがって、これを行う場合は、ジェネリックスを使用して、1日と呼びます。


同じではありません。入力を整数、浮動小数点数、ブール値など、戻り値の型の使用方法に基づいて変換する関数をどのように処理しますか?それぞれに特別なケースが必要なため、一般化することはできません。
ジェイコンロッド

「戻り値の型のオーバーロード」の巧妙な戦略については、codeproject.com / KB / cpp / returnoverload.aspxを参照してください。基本的に、関数func()を定義する代わりに、構造体funcを定義し、それにoperator()()と適切な各型への変換を与えます。
j_random_hacker 2009年

ジェイ、関数を呼び出すときに戻り値の型を定義します。inpusが異なる場合は、まったく問題ありません。同じものが存在する場合は、GetType()を使用してタイプに基づいたいくつかのロジックを備えた汎用バージョンを使用できます。
Charles Graham、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.