Kapan jenis informasi mengalir mundur di C ++?

92

Saya baru saja melihat Stephan T. Lavavej berbicara di CppCon 2018"Pengurangan Argumen Template Kelas", di mana pada titik tertentu dia secara tidak sengaja mengatakan:

Dalam tipe C ++ informasi hampir tidak pernah mengalir ke belakang ... Saya harus mengatakan "hampir" karena ada satu atau dua kasus, mungkin lebih tetapi sangat sedikit .

Meskipun mencoba mencari tahu kasus mana yang mungkin dia maksud, saya tidak dapat menemukan apa pun. Karena itu pertanyaannya:

Dalam kasus apa standar C ++ 17 mengamanatkan bahwa informasi jenis menyebar mundur?

Massimiliano
sumber
pola yang cocok dengan spesialisasi parsial dan tugas penghancuran.
v.oddou

Jawaban:

80

Ini setidaknya satu kasus:

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

jika Anda melakukannya foo f; int x = f; double y = f;, informasi jenis akan mengalir "mundur" untuk mencari tahu apa yang Tada di dalamnya operator T.

Anda dapat menggunakan ini dengan cara yang lebih maju:

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

jadi sekarang saya bisa melakukannya

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

dan berhasil.

Tentu saja, mengapa tidak dilakukan saja {1,2,3}? Yah, {1,2,3}itu bukan ekspresi.

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

yang memang membutuhkan lebih banyak sihir: Contoh langsung . (Saya harus membuat deduce return melakukan pemeriksaan SFINAE dari F, lalu membuat F menjadi SFINAE ramah, dan saya harus memblokir std :: initializer_list di deduce_return_t operator T.)

Yakk - Adam Nevraumont
sumber
Jawaban yang sangat menarik, dan saya belajar trik baru jadi terima kasih banyak! Saya harus menambahkan pedoman pemotongan template untuk membuat contoh Anda dikompilasi , tetapi selain itu berfungsi seperti pesona!
Massimiliano
5
The &&kualifikasi di operator T()adalah sentuhan yang hebat; ini membantu menghindari interaksi yang buruk dengan automenyebabkan kesalahan kompilasi jika autodisalahgunakan di sini.
Justin
1
Itu sangat mengesankan, dapatkah Anda menunjukkan kepada saya beberapa referensi / berbicara tentang ide dalam contoh? atau mungkin asli :) ...
llllllllll
3
@ lili Ide yang mana? Saya menghitung 5: Menggunakan operator T untuk menyimpulkan jenis pengembalian? Menggunakan tag untuk meneruskan tipe yang disimpulkan ke lambda? Menggunakan operator konversi untuk menggulung konstruksi objek penempatan Anda sendiri? Menghubungkan semua 4?
Yakk - Adam Nevraumont
1
@lili Contoh "cara yang lebih maju" adalah, seperti yang saya katakan, hanya 4 atau lebih ide yang direkatkan. Saya melakukan perekatan dengan cepat untuk posting ini, tetapi saya pasti telah melihat banyak pasangan atau bahkan triplet yang digunakan bersama. Ini adalah sekumpulan teknik yang cukup tidak jelas (seperti yang dikeluhkan oleh tootsie), tetapi tidak ada yang baru.
Yakk - Adam Nevraumont
31

Stephan T. Lavavej menjelaskan kasus yang dia bicarakan dalam sebuah tweet :

Kasus yang saya pikirkan adalah di mana Anda dapat mengambil alamat dari fungsi yang kelebihan beban / template dan jika itu digunakan untuk menginisialisasi variabel dari tipe tertentu, itu akan membedakan mana yang Anda inginkan. (Ada daftar hal-hal yang mengganggu.)

kita dapat melihat contohnya dari halaman cppreference di Address of overloaded function , saya telah mengecualikan beberapa di bawah ini:

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

Michael Park menambahkan :

Ini tidak terbatas pada menginisialisasi jenis beton juga. Bisa juga disimpulkan hanya dari jumlah argumen

dan memberikan contoh langsung ini :

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

yang saya uraikan lebih lanjut di sini .

Shafik Yaghmour
sumber
4
Kami juga dapat menggambarkan ini sebagai: kasus di mana jenis ekspresi bergantung pada konteksnya?
MM
20

Saya percaya pada pengecoran statis fungsi kelebihan beban, aliran berjalan ke arah yang berlawanan seperti pada resolusi kelebihan beban biasa. Jadi salah satunya mundur, saya kira.

jbapple.dll
sumber
7
Saya yakin ini benar. Dan itu adalah saat Anda mengirimkan nama fungsi ke tipe penunjuk fungsi; jenis informasi mengalir dari konteks ekspresi (jenis yang Anda tetapkan ke / konstruksi / dll) mundur ke nama fungsi untuk menentukan kelebihan beban yang dipilih.
Yakk - Adam Nevraumont