使用 std::variant 的静态多态性

Posted

技术标签:

【中文标题】使用 std::variant 的静态多态性【英文标题】:Static polymorphism using std::variant 【发布时间】:2021-04-04 17:20:45 【问题描述】:

我正在尝试使用 std::variant 实现静态多态。我想使用 VARIANT_METHOD 或 VARIANT_METHOD_CONST 声明方法,这些方法应采用返回类型、方法名称、参数和限定符。

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

我不能声明两个具有相同名称但具有不同参数的方法。我想写这样的东西:

VARIANT_METHOD_CONST(int, func, (int a), const noexcept);
VARIANT_METHOD_CONST(int, func, (int a, int b), const noexcept);

【问题讨论】:

你可以先解释你正在尝试做什么以及你遇到了什么错误。 嗯...对不起? 不,我的意思是您可以从编辑问题开始,描述您想要实现的目标以及遇到的错误。 I want to write something like this: 好的,结果应该是什么? 我的意思是字面意思,它应该在文本上产生什么结果?所以就写int func(int) int func(int, int): protected std::variant 是否允许从任何 std:: 类继承?为什么不直接使用合成?为什么是所有的宏? 【参考方案1】:

我所知道的最干净的方法是滥用operator-&gt;* 并制作多态方法指针。你可以获得相同的语法(除了-&gt;* 而不是.),没有宏。

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

一点c++20 字母汤。将 auto 参数替换为 template&lt;class T&gt; 等以获得 c++14 版本。

你最终会像这样制作 poly 成员:

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

您的代码如下所示:

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

现在,如果您不喜欢编写那些 constexpr lambda,您可以让它们由宏编写。

这些 poly 方法和成员适用于支持存储在其中的 lambda 的任何类的任何实例。成员和方法的区别在于方法需要一个尾随 () 并传入这些参数,而成员则不需要。

例如:

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

作用于a,就像作用于variant&lt;A, other stuff&gt;

Live example.

如果你真的非常依赖你的语法,我建议你看看 google mock 是如何做到的。

我有时会扩展它并写

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

并为我存储的std::any 动态构建vtables 以访问每个method,给你:

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

在我们输入的地方删除任何支持这些方法的内容。

但那是我个人的精神错乱。 (此扩展要求您使用部分签名信息标记 poly 成员/方法)。

【讨论】:

以上是关于使用 std::variant 的静态多态性的主要内容,如果未能解决你的问题,请参考以下文章

与传统的多态处理相比,使用 std::variant 有啥优势?

为啥 AbstractFactoryUnit 具有动态而不是静态多态性?

C++ 静态多态性 (CRTP) 和使用派生类的 typedef

静态多态性问题

如何使用静态多态性在 int 和指针类型之间进行转换?

详细说明:方法重载是静态/编译时绑定,但不是多态性。将静态绑定与多态性相关联是不是正确?