变体类型和可变参数模板的转换和输出运算符

时间:2016-03-25 05:50:21

标签: c++ templates c++11 c++14 unions

我最近提出了另一个问题,我想知道怎样才能提供一个具有结构的类https://gist.github.com/tibordp/6909880operator string()之类的转换运算符。基本上,代码中说明的想法是联合和变体类型。 union元素是data_t类型的对象。这本身就是具有适当类的std::aligned_storage的typedef。

特别是我正在寻找一种方法来查询给定的模板化类并返回static_cast ed对象。如果查询的类型都不正确,则static_assert应该失败或者常规断言失败。我看到可能有一种方法可以通过给定的模板化类并将它们的typeid()。hash_code()存储在一个集合中,然后查询但这似乎不是最有效的解决方案。

如下所示

template <typename... Vs>
struct variant {
    typename std::aligned_union<Types...>::type storage;
    int hash_code_of_type_stored;

    template <typename Type>
    operator Type() { 
        // static assert if the type is not in Vs... or assert fail
        // otherwise return the appropriate static_cast 
    }
};

这可以通过一个断言将输入类型的hash_code与存储的对象的类型进行比较来实现。但后来这并不令人满意,因为我想以某种方式知道这里存储的对象的类型,以便例如我可以重载operator<<函数以正确使用ostreams。

另外作为旁注。如果在自定义结构上定义了operator string()函数,该结构具有字符串作为成员变量,那么从函数返回的是什么?这只是一个const引用才有意义,但我想确定。

2 个答案:

答案 0 :(得分:1)

我最近实现了自己的可变参数模板,用于检查某个类型是否在参数包中。我从另一个SO用户那里得到了我的解决方案:

Check if C++0x parameter pack contains a type

template <typename... Vs>
struct variant {
    typename std::aligned_union<Types...>::type storage;
    int hash_code_of_type_stored;

    template <typename Type>
    operator Type() { 
        static_assert(contains<Type, Vs...>::value, 
            "parameter pack should contain Type");
    }
};

答案 1 :(得分:1)

这是问题中提出的变体类型的实现。它修复了初始未完成版本中存在的一些问题:

  • 添加了销毁,根据当前存储的值类型在运行时调度。

  • 修复了我忽略的g ++下的不兼容问题,因为我使用的是Visual Studio。如果您发现任何其他编译器不兼容,请告诉我 - Microsoft的扩展程序将很乐意编译许多格式错误的模板,导致g ++失败。

  • 选择将值数据存储在实例化的aligned_union成员对象中

  • 删除了用于查找最大类型大小的模板元程序,因为aligned_union不需要此项。它接受一个对齐大小参数,但这里没有对齐要求,所以为此参数传递0。

  • 现在可以将非POD数据添加到变体中,但是您应该通过强制转换为对该类型的引用来获取存储的值。转换为类型本身可能会调用其构造函数之一,将variant对象传递给它进行转换。如果转换运算符被标记为显式,这可能会删除隐式转换,这会导致编译器在决定如何处理从变量到其类型之一的转换时选择这些不需要的构造函数。

除非出现更多编译器问题,否则我已完成此操作。

#include <iostream>
#include <type_traits>
#include <new>
#include <string>

namespace cvrnt {
// types_match works like std::is_same
template <typename T, typename U>
struct types_match {
    struct true_t { int x; };
    struct false_t { true_t x[2]; };
    template <typename A, typename B>
    static false_t test(const A&, const B&);
    template <typename A>
    static true_t  test(const A&, const A&);
    static constexpr bool value = sizeof(test(T{}, U{})) == sizeof(true_t);
};

template<bool condition, typename T=void> struct enable_if {};
template<class T>                         struct enable_if<true, T> { using type = T; };

// pack_index structs: searches parameter pack for a given type, giving index
template <typename T, int CURI, bool MATCH, typename V, typename ...Vs>
struct pack_index_helper {
    static constexpr int index = pack_index_helper<T, CURI + 1, types_match<T, V>::value, Vs...>::index;
};
template <typename T, int CURI, typename V>
struct pack_index_helper<T, CURI, false, V> {
    static constexpr int index = types_match<T, V>::value ? CURI - 1 : -1;
};
template <typename T, int CURI, typename V, typename ...Vs>
struct pack_index_helper<T, CURI, true, V, Vs...> {
    static constexpr int index = CURI - 1;
};
template <typename T, typename ...Vs>
struct pack_index {
    static constexpr int index = pack_index_helper<T, 0, false, Vs...>::index;
};

////

template <typename T, typename ...Vs>
struct type_in_pack {
    static constexpr bool value = pack_index<T, Vs...>::index >= 0;
};

// Curious' variant
template <typename ...Vs>
struct variant {
    // declare the aligned_union type for this parameter pack (from <type_traits>)
    // no special alignment requirement is specified here
    using union_t = typename std::aligned_union<0, Vs...>::type;
    static constexpr auto sizeof_storage = sizeof(union_t);

    // the "storage" object is used for storage of the variant's values
    // variant values.
    union_t   storage;
    // "stored_type_index" keeps track of the current stored type
    // >=0 indicates the stored value is valid and is of the type
    //     corresponding to this parameter pack index.
    // -1  indicates no value is currently stored
    int stored_type_index;

    // value data are stored in "storage" using placement new copy-construction
    // they are read using pointers, reinterpret_cast to the current type
    // the objects are destroyed with reinterpret_cast pointers, too
    // * Unsure about strict aliasing rule violation when "storage" is an aligned_union object.

    // this StoreValue overload handles non-pointer/non-array values
    template <typename T>
    void StoreValue(const T& value) {
        // throw -2 if storing non-pointer type does not match any pack type
        if((stored_type_index = pack_index<T, Vs...>::index) == -1) { throw - 2; }
        // placement construction
        new (&storage) T{ value };
    }
    // this overload handles pointer values and makes sure array references
    // are stored as decayed pointers
    template <typename T>
    void StoreValue(const T* pvalue) {
        // throw -3 if storing pointer type doesn't match any pack type
        if((stored_type_index = pack_index<T, Vs...>::index) == -1) { throw - 3; }
        // placement construction
        new (&storage) const T*(pvalue);
    }

    template <typename T>
    T& GetValue() {
        if(stored_type_index == -1 || stored_type_index != pack_index<T, Vs...>::index) {
            // GetValue attempted when no value stored, or when stored
            // type doesn't match this GetValue's template type
            throw(stored_type_index);
        }
        return *reinterpret_cast<T*>(&storage);
    }
    template <typename T>
    const T& GetValue() const {
        // use const_cast to leverage non-const version, to avoid duplicate code 
        return const_cast<const T&>(const_cast<variant*>(this)->GetValue<T>());
    }

    // Template conversion (cast) operators
    // Return const or non-const reference to stored value.
    // The parameter list only matches types from the variant's parameter pack
    // other possible conversions are disabled using enable_if and type_in_pack
    template <typename Type, typename=typename enable_if<type_in_pack<Type, Vs...>::value>::type>
    operator const Type& () const { return GetValue<Type>(); }
    template <typename Type, typename = typename enable_if<type_in_pack<Type, Vs...>::value>::type>
    operator Type& () { return GetValue<Type>(); }

    // Run-time dispatch of destructor by stored value type parameter index
    template <int index>
    void destroy_stored_object_helper() {
        // This overload's index is one past the end of the pack
        // It won't ever be used at runtime, but
        // it needs to be here in order to terminate
        // compile-time recursive pack iteration
        // when no parameters are left.
    }

    template <int index, typename T, typename ...Ts>
    void destroy_stored_object_helper() {
        if(stored_type_index == index) {
            // This version's pack index matches the
            // stored type index.  Destroy stored object, then
            // set stored type index to -1, indicating no value stored.
            reinterpret_cast<T*>(&storage)->~T();
            stored_type_index = -1;
        }
        else {
            // the function whose pack index matches "stored_type_index"
            // will be found later on, so call the next type's function.
            destroy_stored_object_helper<index + 1, Ts...>();
        }
    }
    // "destroy_stored_object" performs runtime destructor dispatch
    // for pack type at "stored_type_index"
    // No destructor is called when index is negative.
    void destroy_stored_object() {
        // skip destruction when no value currently stored
        if(stored_type_index < 0) return;
        // start the compile-time generated chain
        // of pack type destructor wrapper functions
        destroy_stored_object_helper<0, Vs...>();
        // this would be more efficient if implemented
        // as an indexed table
    }
    // assignment operator, from an element type
    // first makes sure any current value is destroyed
    // then stores the passed value
    template <typename T>
    variant& operator = (const T& src) {
        destroy_stored_object();
        StoreValue(src);
        return *this;
    }
    // assignment from variant could be added, but
    // deleted here so the template or default assignment won't
    // be used
    variant& operator = (const variant& src) = delete;

    // default ctor sets index to flag no value currently stored
    variant() : stored_type_index(-1) {}

    // Template conversion constructor
    template <typename T>
    variant(const T& src) { StoreValue(src); }

    // ok to implement copy ctor later
    // deleted here because default implementation is bad
    variant(const variant& src) = delete;

    // The move ctor is straightforward:
    // just copy the source object's member values
    // then set the source object's "stored_type_index"
    // to -1, which means there is no stored value
    variant(variant&& src) : storage(src.storage), stored_type_index(src.stored_type_index) {
        src.stored_type_index = -1;
    }

    ~variant() { destroy_stored_object(); }
};
}

int main() {
    try {
        // construct a variant object, initally storing an int value
        cvrnt::variant<int, std::string, double, const char*, char> my_variant(11);
        std::cout << static_cast<int>(my_variant) << '\n';

        // set a std::string.  To fetch non-POD values, cast to a reference
        // in order to avoid using a constructor from the other class.
        my_variant = std::string("Non-POD std::string");
        std::cout << static_cast<const std::string&>(my_variant) << '\n';

        // store a double value 
        my_variant = 100.001;
        std::cout << static_cast<double>(my_variant) << '\n';

        // now store a char pointer in the same variant object
        my_variant = "Hello!";
        std::cout << static_cast<const char*>(my_variant) << '\n';

        //std::cout << my_variant.stored_type_index << '\n';
        std::cout << '\n';
        std::cout << "union storage size: " << my_variant.sizeof_storage << '\n';
    }
    catch (int type_index) {
        // if the variant's value is fetched with the wrong type conversion,
        // or an attempt is made to store a value not in the parameter pack,
        // an exception is thrown.
        std::cout << "Operation failed: " << type_index << '\n';
    }
}