多态变体,并将一种类型的引用与另一种类型的引用绑定

时间:2017-02-16 09:51:43

标签: c++ c++11

我一直在试验我称之为PolymorphicVariant的类,这类似于unique_ptr,除了它将其值存储在内联而不是在堆上分配它。为此我使用了variant类型(自制版本)和一些静态断言来检查所有变体是否实现了给定的接口(即它们都来自相同的基类)。然后我有方法来获取指向基类的指针。然后我可以将PolymorphicVariant存储在容器中,并使其行为与存储指向基类的指针相同,除非没有间接。

template <typename Base, typename... Derivees>
class PolymorphicVariant {
    static_assert(std::is_polymorphic<Base>{},
                  "Base type has to be polymorphic");

    static_assert(meta::And<std::is_base_of<Base, Derivees>...>{},
                  "Derivees have to derive from Base");

    using VariantType = Variant<Derivees...>;
    using Types = meta::List<Derivees...>;

    struct ToBasePtrVisitor {
        template <typename T>
        auto operator()(const T& value) const -> const Base* {
            return &value;
        }
    };

public:
    // ... Constructors and assignment operators ...

    auto get() const -> const Base* {
        return apply(ToBasePtrVisitor{}, m_storage);
    }

private:
    VariantType m_storage;
};

然后我可以做这样的事情:

// Bar and Baz both implement IFoo
using Foo = PolymorphicVariant<IFoo, Bar, Baz>;

std::vector<Foo> foos;

for (int i = 0; i < 10; ++i) {
    if (i % 2 == 0) {
        foos.emplace_back(Bar{});
    } else {
        foos.emplace_back(Baz{});
    }
}

for (const auto& elem : foos) {
    elem.get()->some_virtual_fn();
}

所以现在我的问题。让我们说我有一个函数引用PolymorphicVariant(就像使用指向抽象基类的函数一样):

auto mutate_foo(Foo& foo) -> void {
    foo.get()->mutating_function();
}

Bar my_bar;
mutate_foo(my_bar); // This doesn't work

这不起作用的原因是我无法将Bar的引用绑定到Foo的引用(即PolymorphicVariant<IFoo, Bar, Baz>)。有没有办法让这成为可能?某种方式告诉编译器Bar引用实际上可以被视为Foo引用?无需更改IFooBar的实施。如果不可能,我不得不将mutate_foo的签名更改为mutate_foo(IFoo* foo) -> void,但这会让我觉得不那么干净,因为我想要Foo作为首选类型来表示&#34;包含满足界面IFoo&#34;的东西。谢谢!

3 个答案:

答案 0 :(得分:0)

  

有没有办法让这成为可能?无需更改IFooBar

的实施

我现在想不出有任何办法。如果我能想到一个(好的),我会编辑我的帖子。

  

我希望Foo充当首选类型,用于表示“包含符合接口IFoo的内容的内容”。

您可以使用template功能:

template <typename TFoo>
auto mutate_foo(TFoo& foo) -> void {
    foo.get()->mutating_function();
}

您可能希望使用谓词来约束它,该谓词检查不是TFoo是否实际实现了您想要的界面:

template <typename TFoo, 
          typename std::enable_if_t<implements_foo_interface<TFoo>{}>>
auto mutate_foo(TFoo& foo) -> void {
    foo.get()->mutating_function();
}

implements_foo_interface可以使用detection idiom轻松实现。

答案 1 :(得分:0)

在评论之后,我发现FooBar不需要从公共接口继承。如果在FooBar的名称空间中提供自由函数重载,则变体访问者已经提供了管道以找到正确的方法。

示例(使用boost::variant,因为我无法访问您的库):

#include <boost/variant.hpp>
#include <memory>
#include <iostream>

struct Foo {
    void do_something() { std::cout << "something\n"; }
};


struct Bar {
    // note - dissimilar interfaces
    void do_something_else() { std::cout << "something else\n"; }
};


void mutating_function(Foo& on) {
    // overload explicit actions here
    on.do_something();
}

void mutating_function(Bar& on) {
    // overload explicit actions here
    on.do_something_else();
}

struct invoke_mutating_function
{
    template<class Target>
    void operator()(Target& on) const {
        mutating_function(on);
    }
};


using FooBar = boost::variant<Foo, Bar>;

void mutating_function(FooBar& on)
{
    boost::apply_visitor(invoke_mutating_function(), on);
}

int main()
{
    FooBar x = Foo();
    FooBar y = Bar();

    mutating_function(x);
    mutating_function(y);

}

预期产出:

something
something else

答案 2 :(得分:0)

下面:

   int m = Integer.parseInt( args[0] );
   if(m>=0&&m<13)
   System.out.println( months[ m ] );

创建一个类型auto mutate_foo(Foo& foo) -> void { foo.get()->mutating_function(); }

FooRef

auto mutate_foo(FooRef foo) -> void { foo.get()->mutating_function(); } 基于FooRefPolymorphicAnyRef与您的PolymorphicAnyRef非常相似,但它会将指针存储到源自PolymorphicVariant的内容中,以及获取Base的方式从那个指针。

一个简单的实现是简单地存储Base*并让Base*返回它。

get()

这会增加一些语法糖而不是template<class Base> struct PolymorphicAnyRef { Base* ptr = 0; Base* get() const { return ptr; } // blah, etc template<class T> PolymorphicAnyRef( T& t ): ptr(std::addressof(t)) {} template<class...Ts> PolymorphicAnyRef( PolymorphicVariant<Base, Ts...>& pv ): ptr( pv.get() ) {} }; ,因为它会从Base*或任何引用转换为Base

顺便说一下,我写了PolymorphicVariant<Base,???>而不是PolymorphicVariant来保证存储PolymorphicAny<Base, Ts...>中的任何一个,但也可以存储小于和较少对齐 Ts...足够“常规”(可复制,无论你需要什么)。派生检查是在构造而不是类型实例化时完成的。

然后它可以存储一个函数指针,将Ts...转换为存储的内部void*Any,并将其用于Base*。一个支持添加新类型擦除方法的写得很好的get()可以让你将该操作与默认操作一起存储。

但我有点疯了。