Konversi dari lambda ke jenis non-skalar diminta

Dec 08 2020

Saya membuat kelas ini sehingga saya dapat memiliki nilai jenis apa pun yang diperbaiki atau dihitung ulang setiap kali digunakan:

template<typename T>
class Value {
    private:
    bool fixed;
    union {
        T value;
        std::function<T()> get;
    };
    public:
    Value(const T& value) : fixed(true), value(value) {}
    Value(const std::function<T()>& get) : fixed(false), get(get) {}
    Value(const T *pointer) : Value([pointer]() { return *pointer; }) {}
    ~Value() {}
    operator T() { return fixed ? value : get(); }
};

Semua ekspresi berikut tampaknya berfungsi dengan baik:

Value<double> a = 2.2;
double b = 1.;
double c = a;
Value<double> d = &b;
Value<int> e = Value<int>([]() { return 1.; });

Tetapi ketika saya mencoba melakukan:

Value<double> f = []() { return 1.; };

kesalahan kompilasi dipicu:

error: conversion from 'main()::<lambda()>' to non-scalar type 'Value<double>' requested

Anda dapat mencoba contoh ini di sini .

Mengapa penugasan berhasil Tdan tidak, std::function<T()>dan bagaimana cara membuatnya demikian?

Catatan: Saya mengetahui jawaban ini tetapi saya tidak menjelaskan kepada saya cara memperbaiki masalah tanpa harus secara eksplisit memanggil konstruktor seperti yang saya lakukan Value<double> e.

Jawaban

4 Slava Dec 08 2020 at 00:22

Mengapa penetapan berfungsi untuk T dan bukan std :: function <T ()> dan bagaimana cara membuatnya begitu?

Kode Anda tidak menggunakan tugas, tetapi menyalin inisialisasi dan

Selain itu, konversi implisit dalam inisialisasi salinan harus menghasilkan T langsung dari penginisialisasi, sedangkan, misalnya inisialisasi langsung mengharapkan konversi implisit dari penginisialisasi ke argumen konstruktor T.

Jadi untuk membuatnya berfungsi, Anda harus membuat ctor Anda menerima lambda secara langsung (ini adalah contoh yang disederhanakan):

template<typename T>
class Value {
    std::function<T()> get;    
public:
    
    template<class Y>
    Value(Y lambda ) : get( std::move( lambda ) )  {}
};

live code Anda mungkin ingin menambahkan pembatasan menggunakan std::enable_ifatau konsep jika C ++ 20 diizinkan untuk ctor ini serta dalam bentuk ini konstruktor ini akan mencoba untuk menerima setiap kelebihan beban lain tidak akan dan dapat menghasilkan kesalahan samar. Dan menurut parameter template enable_if ini adalah lambda (dengan tanda tangan tertentu), ini bisa sesederhana

template<class Y, typename = decltype(std::declval<Y&>()())>
Value(Y lambda ) : get( std::move( lambda ) )  {}

yang mendukung C ++ 14. Berikut adalah contoh langsung lainnya di mana Anda dapat melihat bahwa konstruktor ini tidak digunakan untuk penginisialisasi tipe int:

 Value<double> d2 = 123;

prog.cpp: 9: 5: catatan: template kandidat diabaikan: kegagalan substitusi [dengan Y = int]: disebut tipe objek 'int' bukan fungsi atau penunjuk fungsi Nilai (Y lambda): get (std :: move (lambda )) {}

4 NathanOliver Dec 08 2020 at 00:17

Lambda bukanlah std::function. Itu artinya jika Anda melakukannya

Value<double> f = []() { return 1.; };

Anda perlu mengubahnya []() { return 1.; }menjadi std::function, yang merupakan konversi yang ditentukan pengguna, dan kemudian Anda perlu mengubahnya std::functionmenjadi Value<double>, yang merupakan konversi buatan pengguna lainnya. Itu adalah dua konversi yang ditentukan pengguna bila Anda hanya diizinkan untuk satu konversi semacam itu. Inilah sebabnya mengapa kode gagal untuk dikompilasi.