C ++では、型情報はいつ逆方向に流れますか?


92

私はちょうどでステファンT. Lavavejトークを見CppCon 2018に「クラステンプレート引数控除」、上のいくつかの点彼はついでに言います:

C ++の型情報は、逆方向に流れることはほとんどありません... 1つまたは2つのケースがあるため、「ほとんど」と言わざるを得ませんでした。

彼がどのケースを参照しているのかを理解しようとしたにもかかわらず、私は何も思いつきませんでした。したがって、質問:

C ++ 17標準では、その型情報が逆伝播するように義務付けられているのはどの場合ですか?


パターンマッチングの部分的な特殊化と分解の割り当て。
v.oddou 2018年

回答:


80

以下は少なくとも1つのケースです。

struct foo {
  template<class T>
  operator T() const {
    std::cout << sizeof(T) << "\n";
    return {};
  }
};

そうするとfoo f; int x = f; double y = f;、タイプ情報が「逆方向」に流れて、何Tが入っているかがわかりoperator Tます。

これはより高度な方法で使用できます。

template<class T>
struct tag_t {using type=T;};

template<class F>
struct deduce_return_t {
  F f;
  template<class T>
  operator T()&&{ return std::forward<F>(f)(tag_t<T>{}); }
};
template<class F>
deduce_return_t(F&&)->deduce_return_t<F>;

template<class...Args>
auto construct_from( Args&&... args ) {
  return deduce_return_t{ [&](auto ret){
    using R=typename decltype(ret)::type;
    return R{ std::forward<Args>(args)... };
  }};
}

だから今できる

std::vector<int> v = construct_from( 1, 2, 3 );

そしてそれは動作します。

もちろん、なぜそうしないの{1,2,3}ですか?まあ、それ{1,2,3}は表現ではありません。

std::vector<std::vector<int>> v;
v.emplace_back( construct_from(1,2,3) );

確かに、もう少しウィザードが必要です:実例。(私はFのSFINAEチェックを行い、その後、FはSFINAE友好的作るを推論リターンを行う必要があり、そして私はSTDをブロックする必要が:: deduce_return_tオペレータTのinitializer_list)


非常に興味深い答えです。新しいトリックを学びました。どうもありがとうございました。私はあなたの例をコンパイルするためにテンプレート控除ガイドラインを追加しなければなりませんでしたが、それ以外は魅力的に機能します!
Massimiliano

5
&&修飾子operator T()は素晴らしいタッチです。ここで誤用されたauto場合にコンパイルエラーを発生させることにより、との相互作用の低下を回避するのに役立ちautoます。
ジャスティン

1
それは非常に印象的です、例のアイデアへの参照/話を私に指摘できますか?または多分それはオリジナルです:) ...
llllllllll

3
@liliどのアイデア?5を数える:演算子Tを使用して戻り値の型を推定する タグを使用して推定型をラムダに渡しますか?変換演算子を使用して独自の配置オブジェクト構築をロールアップしますか?4つすべてを接続しますか?
Yakk-Adam Nevraumont

1
@lili Thaの「より高度な方法」の例は、私が言ったように、4つほどのアイデアが結びついたものです。私はこの投稿のためにその場で接着を行いましたが、私は確かに多くのペア、またはそれらのトリプレットさえ一緒に使用されるのを見ました。それは(tootsieが不満を言うように)かなりあいまいなテクニックの集まりですが、斬新なものはありません。
Yakk-Adam Nevraumont

31

Stephan T. Lavavejは、彼が話していた事件をツイートで説明しました

私が考えていたのは、オーバーロード/テンプレート化された関数のアドレスを取得できる場所であり、特定の型の変数を初期化するためにそれが使用されている場合、どちらを使用するかが明確になります。(明確にするもののリストがあります。)

この例は、オーバーロードされた関数のアドレスのcppreferenceページから確認できます。

int f(int) { return 1; } 
int f(double) { return 2; }   

void g( int(&f1)(int), int(*f2)(double) ) {}

int main(){
    g(f, f); // selects int f(int) for the 1st argument
             // and int f(double) for the second

     auto foo = []() -> int (*)(int) {
        return f; // selects int f(int)
    }; 

    auto p = static_cast<int(*)(int)>(f); // selects int f(int)
}

マイケルパークは追加します

具象型の初期化にも限定されません。引数の数だけから推測することもできます

このライブの例を提供します:

void overload(int, int) {}
void overload(int, int, int) {}

template <typename T1, typename T2,
          typename A1, typename A2>
void f(void (*)(T1, T2), A1&&, A2&&) {}

template <typename T1, typename T2, typename T3,
          typename A1, typename A2, typename A3>
void f(void (*)(T1, T2, T3), A1&&, A2&&, A3&&) {}

int main () {
  f(&overload, 1, 2);
}

ここでもう少し詳しく説明します


4
これを次のように説明することもできます。式のタイプがコンテキストに依存する場合?
MM

20

オーバーロードされた関数の静的キャスティングは、フローが通常のオーバーロードの解決とは逆の方向に進むと思います。ですから、そのうちの1つは逆です。


7
これは正しいと思います。そして、関数名を関数ポインタ型に渡すときです。タイプ情報は、式のコンテキスト(割り当てているタイプ/作成中/その他)から関数の名前に逆方向に流れ、選択されているオーバーロードを判別します。
Yakk-Adam Nevraumont
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.