Saya mencoba menerapkan sesuatu seperti polimorfisme statis menggunakan std::variant. Saya ingin mendeklarasikan metode menggunakan VARIANT_METHOD atau VARIANT_METHOD_CONST, yang harus menggunakan tipe pengembalian, nama metode, argumen, dan kualifikasi.

#include <variant>

#define VARIANT_METHOD(retType, name, ...) \
    template <typename... Args> retType name (Args&&... args) __VA_ARGS__ {                   \
    return std::visit([...args = std::forward<Args>(args)] <typename T>(T& self) -> retType { \
        return self.name(args...);                                                                   \
    }, static_cast<variant&>(*this));                                                                \
}
#define VARIANT_METHOD_CONST(retType, name, ...) template <typename... Args> \
    retType name (Args&&... args) __VA_ARGS__ {                                                    \
    return std::visit([...args = std::forward<Args>(args)]<typename T>(const T& self) -> retType { \
        return self.name(args...);                                                                        \
    }, static_cast<const variant&>(*this));                                                               \
}
#define VARIANT_FIELD(name) \
    decltype(auto) name() noexcept {                                \
        return std::visit([](auto& self) -> decltype(auto) {        \
            return self.name;                                       \
        }, static_cast<variant&>(*this));                           \
    }                                                               \
    decltype(auto) name() const noexcept {                          \
        return std::visit([](const auto& self) -> decltype(auto) {  \
            return self.name;                                       \
        }, static_cast<const variant&>(*this));                     \
    }

struct A {
    int field;
    int field2;

    int func(int a) const noexcept {
        return a + field;
    }
    int func(int a, int b) const noexcept {
        return a * a;
    }
};

struct B {
    int field2;
    int func(int a) const noexcept {
        return a * a;
    }
    int func(int a, int b) const noexcept {
        return a * a;
    }
};

struct C : protected std::variant<A, B> {
    using variant::variant;

    VARIANT_FIELD(field2);
    VARIANT_METHOD_CONST(int, func, const noexcept); // (1)
};

int main() {
    std::vector<C> vec;
    vec.emplace_back(A{.field = 0, .field2 = 1});
    vec.emplace_back(B{.field2 = 3});

    for (auto& c : vec) {
        c.func(10);
    }
}

Saya tidak dapat mendeklarasikan dua metode dengan nama yang sama, tetapi dengan argumen yang berbeda. Saya ingin menulis sesuatu seperti ini:

VARIANT_METHOD_CONST(int, func, (int a), const noexcept);
VARIANT_METHOD_CONST(int, func, (int a, int b), const noexcept);
-1
Max Pasichnyk 4 April 2021, 20:20

1 menjawab

Jawaban Terbaik

Cara terbersih yang saya tahu adalah dengan menyalahgunakan operator->* dan membuat pointer metode polimorfik. Anda bisa mendapatkan sintaks yang sama (kecuali ->* alih-alih .), tanpa makro.

template<class F>
struct poly_member {
  F f;
  friend decltype(auto) operator->*( auto&& t, poly_member const& self) {
    return self.f(decltype(t)(t));
  }
  template<class...Ts>
  friend decltype(auto) operator->*( std::variant<Ts...>& var, poly_member const& self ) {
    return std::visit( self.f, var );
  }
  template<class...Ts>
  friend decltype(auto) operator->*( std::variant<Ts...>&& var, poly_member const& self ) {
    return std::visit( self.f, var );
  }
  template<class...Ts>
  friend decltype(auto) operator->*( std::variant<Ts...> const& var, poly_member const& self ) {
    return std::visit( self.f, var );
  }
  template<class...Ts>
  friend decltype(auto) operator->*( std::variant<Ts...> const&& var, poly_member const& self ) {
    return std::visit( self.f, var );
  }
};
template<class F>
poly_member(F)->poly_member<F>;
template<class F>
struct poly_method {
  F f;
  auto operator()(auto&&...args)const {
    return poly_member{[&](auto&& t)->decltype(auto){
      return f( decltype(t)(t), decltype(args)(args)... );
    }};
  }
  friend auto operator->*( auto&& t, poly_method const& self) {
    return [&](auto&&...args)->decltype(auto){
      return t->*self.f(decltype(args)(args)...);
    };
  }
  friend auto operator->*( auto* t, poly_method const& self) {
    return (*t)->*self;
  }
};
template<class F>
poly_method(F)->poly_method<F>;

Sedikit .

Anda akhirnya membuat anggota poli seperti ini:

constexpr poly_member field { [](auto&& t)->decltype(auto){ return t.field; } };
constexpr poly_member field2 { [](auto&& t)->decltype(auto){ return t.field2; } };
constexpr poly_method func { [](auto&& t, auto&&...args)->decltype(auto){ return t.func(decltype(args)(args)...); } };

Dan kode Anda terlihat seperti:

for (auto& c : vec) {
    c->*func(10);
}

Sekarang jika Anda tidak suka menulis lambda constexpr itu, Anda bisa membuatnya ditulis oleh makro.

Metode dan anggota poli tersebut bekerja pada setiap instance dari kelas apa pun yang mendukung lambda yang disimpan di dalamnya. Perbedaan antara anggota dan metode adalah bahwa metode memerlukan trailing () dan meneruskan argumen tersebut, dan anggota tidak.

Sebagai contoh:

A a;
a->*func(3);

Bertindak pada a seperti halnya pada variant<A, other stuff>.

Contoh langsung.

Jika Anda benar-benar terikat dengan sintaks Anda, saya sarankan untuk melihat bagaimana google mock melakukannya.

Saya terkadang memperpanjang ini dan menulis

template<auto*...methods>
struct poly_any:private std::any {
  // code that makes the above alphabet soup look cute
};

Dan secara dinamis membangun vtables untuk std::any yang saya simpan untuk mengakses setiap method, memberi Anda:

using C = poly_any< &field, &field2, &func >;
C c = A{};
std::cout << c->*field;
std::cout << c->*func(2,3);

Di mana kita mengetik menghapus apa pun yang mendukung metode.

Tapi itu adalah kegilaan pribadi saya. (ekstensi ini mengharuskan Anda menandai anggota/metode poli dengan informasi tanda tangan parsial).

1
Yakk - Adam Nevraumont 5 April 2021, 12:43