回答:
他の人が戻り値の型でオーバーロードし、言っていることに反している可能とされ、いくつかの近代的な言語によって行わ。通常の反対は、
int func();
string func();
int main() { func(); }
どちらfunc()
が呼び出されているかはわかりません。これはいくつかの方法で解決できます:
int main() { (string)func(); }
。私が定期的に(ab)返す型によってオーバーロードを使用する2つの言語:PerlとHaskell。彼らが何をしているのか説明しよう。
で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はどうやって浮動小数点数を読みたいと思ったのですか?まあ、タイプはsqrt
isです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
}
Foo
をBar
サポートし、メソッドがFoo
内部的にtypeを使用するが、typeを返すとしますBar
。そのようなメソッドが、結果をただちにtypeに強制するコードによって呼び出される場合Foo
、Bar
戻り値の型を使用しても機能する可能性Foo
がありますが、どちらかをお勧めします。ところで、私もそれをする方法を見たいです...
var someVar = someMethod();
ます(または、そのような方法で戻り値を使用しないように指定することもできます)。例えば、流暢インタフェースは可変および不変のバージョンを持っていることから恩恵を受けるかもしれない道具という種類の家族は、そうvar thing2 = thing1.WithX(3).WithY(5).WithZ(9);
だろうWithX(3)
コピーthing1
可変オブジェクト、のmutate X、および可変オブジェクトそのリターンにします。WithY(5)
Yを変更して同じオブジェクトを返します。同様に `WithZ(9)。次に、割り当ては不変の型に変換されます。
関数が戻り値の型によってオーバーロードされ、これらの2つのオーバーロードがあった場合
int func();
string func();
このような呼び出しを見て、コンパイラがこれら2つの関数のどちらを呼び出すかを判断する方法はありません。
void main()
{
func();
}
このため、言語設計者はしばしば戻り値のオーバーロードを許可しません。
一部の言語は、(MSILなど)、しかし、ない戻り値の型でのオーバーロードが可能。もちろん、これらも上記の困難に直面していますが、回避策があり、そのドキュメントを参照する必要があります。
そのような言語では、次のことをどのように解決しますか?
f(g(x))
場合はf
過負荷を持っていたvoid f(int)
とvoid f(string)
し、g
過負荷を持っていたint g(int)
とstring g(int)
?なんらかの曖昧さ回避手段が必要になります。
これが必要になる可能性がある状況では、関数の新しい名前を選択する方が適切だと思います。
非常に類似した別の質問から C ++固有の回答を盗む(重複?):
関数の戻り値の型は、単純にStroustrup(他のC ++アーキテクトからの入力で想定)がオーバーロードの解決を「コンテキストに依存しない」ようにしたかったため、オーバーロードの解決では機能しません。「C ++プログラミング言語、第3版」の7.4.1-「オーバーロードと戻り値の型」を参照してください。
その理由は、個々のオペレーターまたは関数呼び出しの解決をコンテキストに依存しないようにするためです。
彼らは、オーバーロードがどのように呼び出されたかにのみ基づいており、結果がどのように使用されたか(まったく使用されなかった場合)に基づいていないことを望んでいました。実際、多くの関数は結果を使用せずに呼び出されます。そうでない場合、結果はより大きな式の一部として使用されます。私が彼らがこれを決定したときに私が確信した1つの要因は、戻り値の型が解決の一部である場合、複雑なルールで解決する必要がある、またはコンパイラーをスローしなければならないオーバーロードされた関数への多くの呼び出しがあることでした呼び出しがあいまいであるというエラー。
そして、主は知っています、C ++オーバーロードの解決は、現状では十分に複雑です...
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行ずつドロップするのに役立つかどうかに興味がありません。
パターンマッチングは、関数のオーバーロードにいくぶん似ており、時々同様に機能すると思います。ただし、いくつかのプログラムにのみ役立ち、ほとんどの言語で実装するのが難しいため、一般的ではありません。
言語に実装するための、実装がより簡単な機能は他にも無限にあります。
良い答え!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 ++のジレンマに取り組むための方法の最前線にあると言えます:)
戻り値の種類が異なるメソッドをオーバーロードする場合は、デフォルト値のダミーパラメータを追加して、オーバーロードの実行を許可します。ただし、パラメータタイプが異なるようにして、オーバーロードロジックが次に機能することを忘れないでください。
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;
すでに示したように、戻り値の型のみが異なる関数のあいまいな呼び出しは、あいまいさをもたらします。あいまいさは欠陥のあるコードを引き起こします。欠陥のあるコードは避けなければなりません。
あいまいさの試みによって引き起こされた複雑さは、これが良いハックではないことを示しています。知的なエクササイズとは別に、参照パラメーターを持つ手順を使用してみませんか。
procedure(reference string){};
procedure(reference int){};
string blah;
procedure(blah)
doing(thisVery(deeplyNested(), andOften(butNotAlways()), notReally()), goodCode());
.NETでは、1つのパラメーターを使用して一般的な結果から必要な出力を示し、期待どおりの結果に変換することがあります。
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();
}
この例も役立つかもしれません:
#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;
}
これは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 ++には、戻り値の型によって関数のオーバーロードを解決するメカニズムは実際にはありません。知らないうちに、戻り値の型による解決をシミュレートできる他の構造またはメカニズムが存在する場合があります。
これは現代の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」のコードを受け入れることができないのはなぜですか?
ほとんどの静的言語もジェネリックをサポートするようになり、問題が解決されます。前に述べたように、パラメーターdiffがないと、どれを呼び出すかを知る方法がありません。したがって、これを行う場合は、ジェネリックスを使用して、1日と呼びます。