变量类型和变量模板的转换和输出运算符

Conversion and output operator for variant types and variadic templates

本文关键字:变量 运算符 输出 转换 类型      更新时间:2023-10-16

我最近问了另一个问题,我想知道如何为一个类提供如图所示的结构https://gist.github.com/tibordp/6909880使用诸如CCD_ 1的转换运算符。基本上,代码中说明的思想是并集和变体类型。其中并集元素是类型为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<<函数,以便正确地与ostream一起使用。

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

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

检查C++0x参数包是否包含类型

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");
    }
};

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

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

  • 修复了g++下的不兼容问题,因为我使用Visual Studio而忽略了这些问题。如果你看到任何其他编译器不兼容的地方,请告诉我——微软的扩展会很乐意编译许多格式错误的模板,这些模板会导致g++失败。

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

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

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

除非出现更多的编译器问题,否则我就完了。

#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';
    }
}
相关文章: