web-dev-qa-db-ja.com

C ++ 11では戻り型を省略

最近、C++ 11モードのgcc 4.5で次のマクロを使用していることに気付きました。

#define RETURN(x) -> decltype(x) { return x; }

そして、このような関数を書く:

template <class T>
auto f(T&& x) RETURN (( g(h(std::forward<T>(x))) ))

私はこれを行って、関数本体を効果的に2回記述する必要がなく、本体と戻り値の型の変更を同期させておくという不便さを回避しています(私の意見では、これは災害の発生を待っています)。

問題は、この手法が1行の関数でしか機能しないことです。だから私がこのようなものを持っているとき(複雑な例):

template <class T>
auto f(T&& x) -> ...
{
   auto y1 = f(x);
   auto y2 = h(y1, g1(x));
   auto y3 = h(y1, g2(x));
   if (y1) { ++y3; }
   return h2(y2, y3);
}

次に、戻り値の型に恐ろしいものを入れなければなりません。

さらに、関数を更新するたびに戻り値の型を変更する必要があります。正しく変更しないと、運が良ければコンパイルエラーが発生し、最悪の場合は実行時のバグが発生します。変更を2つの場所にコピーして貼り付け、それらを同期させる必要があるのは、良い習慣ではないと思います。

また、明示的なキャストの代わりに、戻り時に暗黙的なキャストが必要な状況は考えられません。

確かに、コンパイラにこの情報を推測するように依頼する方法があります。コンパイラがそれを秘密にしておくポイントは何ですか? C++ 11はそのような複製が必要ないように設計されていると思いました。

41
Clinton

g ++ 4.8 が自動戻り型の控除を実装しているようです。このパッチは、その機能についてC++-1Yに関する論文も送付しているJason Merrillによって作成されました。この機能は、-std = c ++ 1yで使用できます。

まだ遊んでいます。

23
emsr

この動作の理論的根拠はドラフト8.3.5p12に記載されています。

Trailing-return-typeは、declarator-idの前に指定するのがより複雑なタイプに最も役立ちます。

template <class T, class U> auto add(T t, U u) -> decltype(t + u);

のではなく

template <class T, class U> decltype((*(T*)0) + (*(U*)0)) add(T t, U u);

したがって、これは実際には、パラメータ名を参照することが役立つ場合を単純化することのみを目的としています。

C++がalwaysが関数の本体から関数の戻り値の型を推測できると仮定した場合、これは飛行しません。 C++(およびC)の目標は、宣言と実装を分離してモジュール化を可能にすることです。そのため、呼び出しの時点では、関数の本体を使用できない場合があります。ただし、すべての呼び出し元は、呼び出されるすべての関数/メソッドのパラメーター型および戻り値の型を知っている必要があります。

11

戻り値の型を設定するだけの場合は、テンプレートの引数にします。これにより、実際に関数を変更することなく、戻り値の型に関連するすべてを変更できます。この例のように、必要に応じてデフォルトの戻り値の型を設定できます。

template <class R = int, class T>
R f(T&& x)
{
   ...
   return h2(y2, y3);
}

以下のコードはその効果を示しています。

デモコード:

#include <iostream>
#include <iomanip>

template <class T, class S>
T h2(T& x, S& y)
{
  return x + y;
}

template <class R = int, class T>
R f(T& x)
{
  auto y2 = x;
  auto y3 = x;
  return h2(y2, y3);
}

int main(int argc, char** argv)
{
  int x = 7;
  std::string str = "test! ";

  auto d = f<double>(x);
  auto i = f(x); // use default type (int)
  auto s = f<std::string>(str);

  std::cout << std::fixed << std::setprecision(4);
  std::cout << "double: " << d << std::endl;
  std::cout << "int: " << i << std::endl;
  std::cout << "string: " << s << std::endl;

  return 0;
}

出力:

double: 14.0000
int: 14
string: test! test!

残念ながら、探している正確な機能は(まだ)存在せず、C++ 0x仕様の一部ではありません。ただし、ドラフト時にC++ 1x仕様の一部である可能性があります。それまでは、テンプレートを使用してください。

7
Gravis

編集:おっと、私はtrailing-return-type指定子とreturnステートメントの間にスコープの違いがあることに気づきました。具体的には:

auto f(int a)
{
    char r[sizeof(f(a))+1];
    return r;
}

カブーム!


以前の答え:

この場合、コンパイラが戻り値の型を推測するための構文が言語で提供されていないのは残念です。推測が可能であることを示すのは簡単だからです。

具体的には、関数内にreturnステートメントが1つだけある場合について説明しています。

関数内のreturnステートメントがどこにあるか、または前のコードがどれほど複雑であるかに関係なく、次の変換が可能であることは明らかです。

return (ugly expression);

auto return_value = (ugly expression);
return return_value;

コンパイラーがreturn_valueのタイプを推測できる場合(およびC++ 0xの規則に従って可能である場合)、推測されたタイプのreturn_valueを関数の戻り値のタイプとして選択できます。

したがって、C++ 0xを変更して、末尾の戻り値の型指定子が必要になるのは、returnステートメントの多重度が正確に1でない場合にのみ実現可能であり、問​​題を解決できるように思えます。

6
Ben Voigt

イットリルに同意します。戻り型の推定は、Haskellのような言語で実現可能な方法であることがすでに証明されており、C++はすでに「auto」を実現しているため、戻り型の推定を実現するためのもう1つのステップにすぎません。テンプレートに提供された実際のタイプの情報が必要であるため、この推論はテンプレート定義ではなく、特殊化の際に行われる必要があります。宣言と定義の分離は、もはや一般的なC++では一般的な方法ではありません。これは、テンプレート本体をヘッダーファイルに記述する必要があるため、テンプレート本体はほとんど常にテンプレート宣言に対応しているためです。複数のreturnステートメントがあり、それらのタイプが一致しない状況では、コンパイラーはエラーを報告することができます。要約すると、委員会が望めば、C++では戻り値の型の控除が完全に可能です。そして、それは非常に重要です。なぜなら、戻り値の型を手動で書くことの重複は、小さなジェネリックヘルパー関数の普及を妨げるからです。これは、関数型プログラミングとジェネリックプログラミングの一般的な慣行です。

4
Peng Wang

OcamlやFelixを含む多くのプログラミング言語では、関数の戻り値の型を推定できるため、関数を指定する必要はありません。 Ocamlでは、インターフェイスで指定できます。 Felixでは、いくつかの関数について、ライブラリコードで指定して、使いやすくすることをお勧めします。

「auto」が戻り値の型で機能しないことに驚いています。それは確かに問題でした。これは実装が難しすぎると思われましたか? [関数が再帰的であることを考えると、それは簡単なことではありません]。

ああ..わかりました。問題は、愚かなテンプレート設計機能の「依存名」です。

template<class T> auto f(T a) { return a.f(); }

したがって、オーバーロードのために通常の関数の戻り値の型を計算するのは少し難しいですが、依存する名前のルックアップのためにテンプレートでは不可能です。インスタンス化の後でしか実行できません。ただし、その前に過負荷が発生する必要があります。したがって、テンプレートに一般化されていないため、自動戻り値の型は言語で使用できません。

2
Yttrill