Mengonversi lambda menjadi fungsi std::tr1::

Menggunakan visual studio 2008 dengan paket layanan tr1 dan Intel C++ Compiler 11.1.071 [IA-32], ini terkait dengan pertanyaan

Saya mencoba menulis peta fungsional untuk c++ yang akan berfungsi seperti versi Ruby

strings = [2,4].map { |e| e.to_s }

Jadi saya telah mendefinisikan fungsi berikut di namespace VlcFunctional

template<typename Container, typename U>
vector<U> map(const Container& container, std::tr1::function<U(Container::value_type)> f)
{
    vector<U> transformedValues(container.size());
    int index = -1; 
    BOOST_FOREACH(const auto& element, container)
    {
        transformedValues.at(++index) = f(element);
    }
    return transformedValues; 
}

dan Anda dapat menyebutnya seperti ini (Perhatikan bahwa argumen templat fungsi didefinisikan secara eksplisit):

vector<int> test;
test.push_back(2); test.push_back(4); 
vector<string> mappedData2 = VlcFunctional::map<vector<int>,string>(test, [](int i) -> string
{
    return ToString(i);
});

Atau seperti itu (Perhatikan bahwa argumen templat fungsi tidak didefinisikan secara eksplisit)

std::tr1::function f = [](int i) -> string { return ToString(i); };
vector<string> mappedData2 = VlcFunctional::map<vector<int>,string>(test, f);

Tapi yang penting, BUKAN SEPERTI INI

vector<string> mappedData2 = VlcFunctional::map(test, [](int i) -> string { return ToString(i); });

Tanpa definisi eksplisit dari argumen template hte, ia tidak mengetahui template mana yang akan digunakan dan menyebabkan kesalahan kompilasi

 ..\tests\VlcFunctional_test.cpp(106): error: no instance of function template "VlcFunctional::map" matches the argument list, argument types are: (std::vector<int, std::allocator<int>>, __lambda3)

Keharusan mendefinisikan argumen templat membuatnya menjadi sintaksis yang jauh lebih besar dan saya bertujuan untuk meminimalkan kesalahan di situs panggilan - ada ide mengapa ia tidak tahu bagaimana melakukan konversi? Apakah ini masalah kompiler atau apakah bahasanya tidak mengizinkan inferensi argumen templat jenis ini?


person Jamie Cook    schedule 09.09.2010    source sumber
comment
Minor: untuk tujuan kompatibilitas, Anda harus menggunakan typename Container::value_type dalam deklarasi std::tr1::function. Visual memaafkan, tetapi kompiler lain tidak.   -  person Matthieu M.    schedule 09.09.2010
comment
Sejak kapan ada dukungan ekspresi lambda C++0x di VS2008? sejauh yang saya tahu mereka baru saja ditambahkan ke VS2010.   -  person snk_kid    schedule 09.09.2010
comment
saya menggunakan visual studio 2008 yang dikombinasikan dengan kompiler intel 11.1   -  person Jamie Cook    schedule 09.09.2010
comment
Anda harus menambahkan kompiler sebenarnya ke kalimat pertama tempat Anda mengidentifikasi lingkungan. Saat ini, dukungan untuk fitur c++0x belum lengkap, setidaknya, di kompiler yang berbeda, dan ada beberapa keanehan di sana-sini...   -  person David Rodríguez - dribeas    schedule 09.09.2010


Jawaban (1)


Masalahnya adalah lambda bukan std::function meskipun dapat dikonversi. Saat menyimpulkan argumen tipe, kompiler tidak diperbolehkan melakukan konversi pada argumen sebenarnya yang disediakan. Saya akan mencari cara agar kompiler mendeteksi tipe U dan membiarkan argumen kedua bebas untuk disimpulkan oleh kompiler:

template <typename Container, typename Functor>
std::vector< XXX > VlcFunctional::map( Container &, Functor )...

Sekarang masalahnya adalah apa yang harus ditulis dalam XXX. Saya tidak memiliki kompiler yang sama dengan Anda, dan semua fitur C++0x masih sedikit rumit. Pertama-tama saya akan mencoba menggunakan decltype:

template <typename Container, typename Functor>
auto VlcFunctional::map( Container & c, Functor f ) -> std::vector< decltype(f(*c.begin())) > ...

Atau mungkin mengetikkan ciri-ciri jika kompiler belum mendukung decltype.

Perhatikan juga bahwa kode yang Anda tulis cukup unik di C++. Biasanya ketika memanipulasi container, fungsinya diimplementasikan dalam bentuk iterator, dan seluruh peta Anda pada dasarnya adalah std::transform yang lama:

std::vector<int> v = { 1, 2, 3, 4, 5 };
std::vector<std::string> s;
std::transform( v.begin(), v.end(), std::back_inserter(s), [](int x) { return ToString(x); } );

Di mana std::transform adalah versi C++ dari fungsi map Anda. Meskipun sintaksisnya lebih rumit, keuntungannya adalah Anda dapat menerapkannya ke penampung apa pun, dan menghasilkan keluaran ke penampung lain, sehingga penampung yang diubah tidak ditetapkan ke std::vector.

EDIT: Pendekatan ketiga, mungkin lebih mudah diterapkan dengan dukungan kompiler Anda saat ini adalah secara manual hanya menyediakan tipe pengembalian lambda sebagai argumen templat, dan membiarkan kompiler menyimpulkan sisanya:

template <typename LambdaReturn, typename Container, typename Functor>
std::vector<LambdaReturn> map( Container const & c, Functor f )
{
   std::vector<LambdaReturn> ret;
   std::transform( c.begin(), c.end(), std::back_inserter(ret), f );
   return ret;
}
int main() {
   std::vector<int> v{ 1, 2, 3, 4, 5 };
   auto strs = map<std::string>( v, [](int x) {return ToString(x); });
}

Bahkan jika Anda ingin menambahkan gula sintaksis ke fungsi map Anda, Anda tidak perlu menerapkannya secara manual saat Anda dapat menggunakan fungsi yang ada.

person David Rodríguez - dribeas    schedule 09.09.2010
comment
David, terima kasih atas jawabannya. Saya mengetahui fungsi transformasi dan ini adalah panggilan rumit yang saya coba hindari - sekaligus meningkatkan pemahaman saya tentang C++. - person Jamie Cook; 09.09.2010
comment
Saya tidak begitu yakin dengan sintaksis yang Anda usulkan dengan pendekatan decltype... Saya menggunakan kompiler intel 11.1 c++ yang seharusnya mendukung decltype tetapi saya mendapatkan banyak kesalahan kompilasi menggunakan sintaksis Anda - di mana saya dapat menemukan lebih banyak info tentang pendekatan ini? - person Jamie Cook; 09.09.2010
comment
@Jamie Cook: Saya kira saya seharusnya mengatakan dukungan untuk tipe pengembalian tambahan... Referensi yang mudah dibaca adalah wikipedia, dalam standar Anda harus membuka bagian 8.5[dcl.decl]/4 untuk sintaksisnya, /5 menyediakan kasus penggunaan. - person David Rodríguez - dribeas; 09.09.2010
comment
David, terima kasih atas bantuan Anda - Saya tahu bahwa kompiler intel c++ berfungsi dengan [auto variabelName = functionCall();] biasa tetapi tampaknya tidak mendukungnya untuk tipe pengembalian. - person Jamie Cook; 09.09.2010
comment
saya juga tidak menyadari bahwa Anda dapat mendefinisikan sebagian daftar argumen - ini adalah solusi sempurna untuk saat ini. Mudah-mudahan para intel bisa mengambil tindakan bersama dan mengizinkan saya bermain-main dengan solusi decltype Anda pada tahap tertentu - untuk saat ini saya senang dengan apa yang telah kami hasilkan. Terima kasih lagi! - person Jamie Cook; 09.09.2010
comment
Saya telah menghapus komentar sebelumnya. Saya gagal memberikan argumen -std=c++0x, jadi kesalahan yang saya laporkan tidak sesuai. Tetap saja tipe pengembalian tambahan belum didukung, dan ini adalah fitur yang bagus untuk dimiliki... - person David Rodríguez - dribeas; 09.09.2010