模板结构包含两种类型,但当时只有一种

时间:2017-03-08 14:28:34

标签: c++ templates

我正在尝试实现一个模板结构,它允许我在同一个数据结构上保存两种数据类型(事件彼此不兼容)。我知道有一些boost :: any已经做了类似于我想要实现的东西,但我无法为我的项目添加提升。

到目前为止我所获得的内容在以下代码中可用于MSVC,但是甚至不能使用Clang在Xcode上编译,告诉我Cannot specialize a function 'Get' within class scope每个Get方法的特殊化我的结构。

我正在努力实现的目标是什么(提升它是如此,我认为它是)?如何修复我的代码才能使其正常工作?

template<typename TypeA, typename TypeB>
struct Either
{
public:
    // ...
    // CODE NOT USEFUL TO THE QUESTION HERE
    // ...

    /** Returns true when the value is meaningful; false if calling GetValueA() or GetValueB() is undefined. */
    bool IsSet() const { return _typeASet || _typeBSet; }
    inline explicit operator bool() const { return _typeASet || _typeBSet; }

    /** Returns true when the value is meaningful; false if calling GetValueA() is undefined. */
    bool IsASet() const { return _typeASet; }

    /** Returns true when the value is meaningful; false if calling GetValueB() is undefined. */
    bool IsBSet() const { return _typeBSet; }

    /** Fallback for wrong type */
    template<typename T>
    const T& Get() const
    {
        static_assert(false, "Cannot get a value which is neither TypeA nor TypeB");
    }

    /** Fallback for wrong type */
    template<typename T>
    T& Get()
    {
        static_assert(false, "Cannot get a value which is neither TypeA nor TypeB");
    }

    /** Specialize to get TypeA */
    template<>
    const TypeA& Get() const
    {
        if(!IsASet())
            throw "Invalid operation";
        return *(TypeA*)&_valueA;
    }

    /** Specialize to get TypeA */
    template<>
    TypeA& Get()
    {
        if(!IsASet())
            throw "Invalid operation";
        return *(TypeA*)&_valueA;
    }

    /** Specialize to get TypeB */
    template<>
    const TypeB& Get() const
    {
        if(!IsBSet())
            throw "Invalid operation";
        return *(TypeB*)&_valueB;
    }

    /** Specialize to get TypeB */
    template<>
    TypeB& Get()
    {
        if(!IsBSet())
            throw "Invalid operation";
        return *(TypeB*)&_valueB;
    }

private:
    /** Indicates that the TypeA values is the one being */
    bool _typeASet;

    /** Indicates that the TypeB values is the one being */
    bool _typeBSet;

    /** Placeholder for TypeA value */
    TypeCompatibleBytes<TypeA> _valueA;

    /** Placeholder for TypeB value */
    TypeCompatibleBytes<TypeB> _valueB;
};

我想保持以下语法:

Either<int, float> value(5);                    // Holds the int value

int intVal = value.Get<int>();                  // Works returning 5
float fltVal = value.Get<float>();              // Compiles but throws exeption (the in value is the valid one)
std::string strVal = value.Get<std::string>();  // Doesn't compile due to static_assert inside Get

2 个答案:

答案 0 :(得分:1)

问题是你不能在成员范围内放置成员模板的专业化;他们需要在命名空间范围内。遗憾的是,您不能仅仅将这些特化项移到类外,因为成员模板的特化还需要专门化包含类模板。解决方法是改为标记调度:

template <typename T> struct type{};

const TypeA& Get(type<TypeA>) const
{
    if(!IsASet())
        throw "Invalid operation";
    return *(TypeA*)&_valueA;
}

TypeA& Get(type<TypeA>)
{
    if(!IsASet())
        throw "Invalid operation";
    return *(TypeA*)&_valueA;
}

const TypeB& Get(type<TypeB>) const
{
    if(!IsBSet())
        throw "Invalid operation";
    return *(TypeB*)&_valueB;
}

TypeB& Get(type<TypeB>)
{
    if(!IsBSet())
        throw "Invalid operation";
    return *(TypeB*)&_valueB;
}

template<typename T>
T& Get()
{
    return Get(type<T>{});
}

template<typename T>
const T& Get() const
{
    return Get(type<T>{});
}

这提供了单独的重载而不是特化,然后通过传递标记作为参数从重载中进行选择。请注意,我没有尝试编译此代码,但它应该给你一个jist。

答案 1 :(得分:1)

这个怎么样:

template <typename T, typename A, typename B>
struct Get; // leave unimplemented!

template <typename TypeA, typename TypeB>
struct Either
{
    enum State
    {
        E,
        A,
        B,
    } state;

    unsigned char data[sizeof(TypeA) > sizeof(TypeB) ? sizeof(TypeA) : sizeof(TypeB)];

    template <typename T, typename A, typename B>
    friend struct Get;

public:
    inline explicit operator bool() const { return isSet(); }
    bool isSet() const { return state != E; }
    bool isASet() const { return state == A; }
    bool isBSet() const { return state == B; }

    template<typename T>
    T& get() const
    {
        return Get<T, TypeA, TypeB>::get(this);
    }

    Either& operator=(TypeA const& a)
    {
        if(isBSet())
        {
            ((TypeB*)data)->~TypeB();
        }
        if(isASet())
        {
            *(TypeA*)data = a;
        }
        else
        {
            new (data)TypeA(a);
            state = A;
        }
        return *this;
    }

    Either& operator=(TypeB const& b)
    {
        if(isASet())
        {
            ((TypeA*)data)->~TypeA();
        }
        if(isBSet())
        {
            *(TypeB*)data = b;
        }
        else
        {
            new (data)TypeB(b);
            state = B;
        }
        return *this;
    }
};

template <typename A, typename B>
struct Get<A, A, B>
{
    static A& get(Either<A, B> const* e)
    {
        if(e->isASet())
        {
            return *(A*)e->data;
        }
        throw "Invalid operation";
    }
};
template <typename A, typename B>
struct Get<B, A, B>
{
    static B& get(Either<A, B> const* e)
    {
        if(e->isBSet())
        {
            return *(B*)e->data;
        }
        throw "Invalid operation";
    }
};

我避免使用包含非POD数据类型的联合,而是使用一个简单的unsigned char数组来保存数据,通过放置构造函数复制数据。

要应对的另一个问题是显式模板特化需要驻留在命名空间范围内。嗯,很好,让我们在命名空间范围内移动它们 - 创建一个模板类,在两个特化中提供适当的getter,并从内部模板函数调用它。至于坏数据类型,没有可用的专业化,编译将适当失败。

当前实现依赖于复制构造函数和赋值运算符可用于这两种类型,但是......

int main (void)
{
    Either<std::string, std::vector<int> > e;
    e = "hello";
    std::cout << e.get<std::string>() << std::endl;
    e = std::vector<int>();
    e.get<std::vector<int> >().push_back(10);
    std::cout << e.get<std::vector<int> >().back() << std::endl;

    Either<int, double > ee;
    ee = 12;
    std::cout << ee.get<int>() << std::endl;
    ee = 7.0;
    std::cout << ee.get<double>() << std::endl;

    return 0;
}

嗯,这个测试至少是成功的......