web-dev-qa-db-ja.com

C++で型情報が逆方向に流れるのはいつですか。

Stephan T. Lavavejが "Class Template Argument Deduction"のCppCon 2018で話すのを見たところ、 ある時点 で彼は偶然にこう言います:

C++の型情報はほとんど逆向きに流れません...1つか2つの場合があるので、「ほぼ」と言わなければなりませんでしたが、おそらくもっと多いがごくわずかです

彼がどの事件について言及しているのか把握しようとしたにもかかわらず、私は何も思いつかなかった。それ故質問:

どの場合に、C++ 17標準は型情報を逆方向に伝播することを義務付けていますか?

90
Massimiliano

これは少なくとも1つのケースです。

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

foo f; int x = f; double y = f;を実行すると、型情報が「逆方向」に流れて、Toperator 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) );

確かに、もう少し魔法使いが必要です: 実例 。 (私はdeduce returnにFのSFINAEチェックを行わせ、そしてFをSFINAEフレンドリーにしなければなりません。 そして deduce_return_t演算子Tのstd :: initializer_listをブロックしなければなりません。)

Stephan T. Lavavej氏がツイートで話していた事例について説明した

私が考えていたのは、あなたがオーバーロードされた/テンプレート化された関数のアドレスを取得することができ、それが特定の型の変数を初期化するために使われている場合です。 (曖昧さを解消するもののリストがあります。)

cppreferenceページのAddress:オーバーロードされた関数のアドレス から、この例を見ることができます。

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);
}

私はもう少し詳しく説明します もっとここに

29
Shafik Yaghmour

オーバーロードされた関数の静的キャストでは、フローは通常のオーバーロードの解決方法とは逆の方向に進みます。それで、それらの1つは後ろ向きです、と私は思います。

19
jbapple