Saya telah menulis kelas Flags sederhana, tetapi menghadapi masalah dengan definisi operator saya. Sepertinya saya mengandalkan beberapa aturan konversi implisit yang tidak ada.

enum class SomeEnum { ONE, TWO, THREE };

template<typename Enum>
class Flags {
    Flags(const Flags& f) { };
    Flags(Enum e) { };

    Flags& operator |=(Flags<Enum> f) {
        //...;
        return *this;
    }
};

template<typename Enum>
Flags<Enum> operator |(Enum a, Flags<Enum> b) { return b | a; }

template<typename Enum>
Flags<Enum> operator |(Flags<Enum> a, Enum b) { return a |= b; }

int main() {
    Flags<SomeEnum> flags = SomeEnum::ONE | SomeEnum::TWO;
    return 0;
}

Saat kompilasi saya mendapatkan kesalahan ini:

implicit.cpp: In function ‘int main()’:  
implicit.cpp:26:40: error: no match for ‘operator|’ (operand types are ‘SomeEnum’ and ‘SomeEnum’)  
  Flags<SomeEnum> flags = SomeEnum::ONE | SomeEnum::TWO;

Pemahaman saya adalah, bahwa salah satu SomeEnum secara implisit diubah menjadi Flags<Enum>, dan kemudian diteruskan ke operator yang benar. Aturan apa yang saya lewatkan?

EDIT:

Saya telah melihat https://stackoverflow.com/questions/9787593, tetapi solusi yang diusulkan (operator nonanggota-teman) tidak menyelesaikan masalah saya. Saya memang menghapus definisi global dan menambahkan anggota ini:

friend Flags operator |(Enum a, Flags b) { return b | a; }
friend Flags operator |(Flags a, Enum b) { return a |= b; }

Tapi kesalahannya masih sama (demo langsung).

2
nyronium 10 November 2017, 23:53

1 menjawab

Jawaban Terbaik

Untuk mengetahui bahwa SomeEnum dapat dikonversi menjadi Flags<SomeEnum>, argumen template Enum harus disimpulkan sebagai SomeEnum, tetapi tidak dapat menyimpulkannya dari argumen karena keduanya tidak cocok dengan Flags<Enum>.

Yaitu pengurangan argumen template harus terjadi sebelum konversi ke tipe lain dapat diperiksa.

Anda dapat menyesuaikan fungsi sehingga hanya satu argumen yang berpartisipasi dalam pengurangan argumen:

template<typename T> struct nondeduced { using type = T; }

template<typename Enum>
Flags<Enum> operator |(Enum a, Flags<typename nondeduced<Enum>::type> b)

Atau setara:

template<typename E> struct FlagType { using type = Flags<E>; }

template<typename Enum>
Flags<Enum> operator |(Enum a, typename FlagType<Enum>::type b);

Ini menggunakan argumen template Enum dalam konteks non-deduksi, jadi hanya argumen lain yang digunakan untuk deduksi.

Tetapi setelah melakukan itu Anda sekarang memiliki kelebihan yang ambigu, karena tidak ada cara bagi kompiler untuk mengetahui apakah Anda ingin argumen pertama dikonversi, atau argumen kedua.

Anda perlu menambahkan kelebihan yang membutuhkan dua jenis SomeEnum, dan melakukan konversi ke Flags<SomeEnum> secara eksplisit. Jika tipe enumerasi seharusnya digunakan dengan operator| maka itu harus mendefinisikan operator itu sendiri.

Opsi lain yang tidak melibatkan pengubahan tipe enumerasi adalah dengan menambahkan helper yang mengubah enumerator menjadi objek Flags:

template<typename Enum>
inline Flags<Enum> flags(Enum e) { return Flags<Enum>(e); }

Maka Anda dapat mengatakan SomeEnum::TWO | flags(SomeEnum::TWO) yang menghindari ambiguitas.

Sebagai poin gaya, jangan gunakan ALL_CAPS untuk enumerator.

3
Jonathan Wakely 10 November 2017, 23:51