boost::variant ではどのように文字列定数が許可されるのでしょうか?
-
09-12-2019 - |
質問
それで、タイプリストをいじってみたのですが、面白いですか。私がやりたかったことの 1 つは、独自の実装を試みることでした variant
このクラスは、タイプリストがどのように機能し、どのように役立つかについての教育の実験として行われます。現在の私のコードは次のようになります。
#include <cstddef>
#include <typeinfo>
#ifndef VARIANT_H_
#define VARIANT_H_
struct NullType {};
template <class T, class U>
struct TypeList {
typedef T Head;
typedef U Tail;
};
#define TYPELIST_1(T1) TypeList<T1, NullType>
#define TYPELIST_2(T1, T2) TypeList<T1, TYPELIST_1(T2) >
#define TYPELIST_3(T1, T2, T3) TypeList<T1, TYPELIST_2(T2, T3) >
#define TYPELIST_4(T1, T2, T3, T4) TypeList<T1, TYPELIST_3(T2, T3, T4) >
#define TYPELIST_5(T1, T2, T3, T4, T5) TypeList<T1, TYPELIST_4(T2, T3, T4, T5) >
#define TYPELIST_6(T1, T2, T3, T4, T5, T6) TypeList<T1, TYPELIST_5(T2, T3, T4, T5, T6) >
#define TYPELIST_7(T1, T2, T3, T4, T5, T6, T7) TypeList<T1, TYPELIST_6(T2, T3, T4, T5, T6, T7) >
#define TYPELIST_8(T1, T2, T3, T4, T5, T6, T7, T8) TypeList<T1, TYPELIST_7(T2, T3, T4, T5, T6, T7, T8) >
#define TYPELIST_9(T1, T2, T3, T4, T5, T6, T7, T8, T9) TypeList<T1, TYPELIST_8(T2, T3, T4, T5, T6, T7, T8, T9) >
namespace util {
namespace {
template <class TL> struct MaxSize;
template <class TL> struct Length;
template <class TL, class T> struct IndexOf;
template <class TL, unsigned int i> struct TypeAt;
template <>
struct MaxSize<NullType> {
static const size_t value = 0;
};
template <class Head, class Tail>
struct MaxSize<TypeList<Head, Tail> > {
static const size_t value = (sizeof(Head) > MaxSize<Tail>::value) ? sizeof(Head) : MaxSize<Tail>::value;
};
template <>
struct Length<NullType> {
enum { value = 0 };
};
template <class Head, class Tail>
struct Length<TypeList<Head, Tail> > {
enum { value = 1 + Length<Tail>::value };
};
template <class T>
struct IndexOf<NullType, T> {
enum { value = -1 };
};
template <class Tail, class T>
struct IndexOf<TypeList<T, Tail>, T> {
enum { value = 0 };
};
template <class Head, class Tail, class T>
struct IndexOf<TypeList<Head, Tail>, T> {
enum { value = (IndexOf<Tail, T>::value == -1) ? -1 : 1 + IndexOf<Tail, T>::value };
};
template <class Head, class Tail>
struct TypeAt<TypeList<Head, Tail>, 0> {
typedef Head type;
};
template <class Head, class Tail, unsigned int i>
struct TypeAt<TypeList<Head, Tail>, i> {
typedef typename TypeAt<Tail, i - 1>::type type;
};
}
template <class TL>
class variant;
template<class U, class TL>
U *get(variant<TL> *v);
template<class U, class TL>
const U *get(const variant<TL> *v);
template<class U, class TL>
U &get(variant<TL> &v);
template<class U, class TL>
const U &get(const variant<TL> &v);
// this stuff is a visitation pattern used to make sure
// that contained objects get properly destroyed
namespace {
template <class TL>
struct apply_visitor;
struct destroy_visitor {
template <class T>
void operator()(T *p) {
p->~T();
}
};
template <class H, class T>
struct visitor_impl {
template <class U, class Pred>
static void visit(U *p, Pred pred) {
if(H *x = get<H>(p)) {
pred(x);
} else {
apply_visitor<T>::visit(p, pred);
}
}
};
template <class H>
struct visitor_impl<H, NullType> {
template <class U, class Pred>
static void visit(U *p, Pred pred) {
if(H *x = get<H>(p)) {
pred(x);
} else {
throw std::bad_cast();
}
}
};
template <class TL>
struct apply_visitor {
typedef typename TL::Head H;
typedef typename TL::Tail T;
template <class U, class Pred>
static void visit(U *p, Pred pred) {
visitor_impl<H, T>::visit(p, pred);
}
};
}
template <class TL>
class variant {
template<class U, class X> friend U *get(variant<X> *v);
template<class U, class X> friend const U *get(const variant<X> *v);
template<class U, class X> friend U &get(variant<X> &v);
template<class U, class X> friend const U &get(const variant<X> &v);
public :
variant() : type_index_(0){
new (&storage_) typename TypeAt<TL, 0>::type();
}
~variant() {
apply_visitor<TL>::visit(this, destroy_visitor());
}
template <class T>
variant(const T &x) : type_index_(IndexOf<TL, T>::value) {
typedef typename TypeAt<TL, IndexOf<TL, T>::value>::type value_type;
new (&storage_) value_type(x);
}
template <class T>
variant(T &x) : type_index_(IndexOf<TL, T>::value) {
typedef typename TypeAt<TL, IndexOf<TL, T>::value>::type value_type;
new (&storage_) value_type(x);
}
template <class T>
variant &operator=(const T &rhs) {
variant(rhs).swap(*this);
return *this;
}
variant &operator=(const variant &rhs) {
variant(rhs).swap(*this);
return *this;
}
public:
void swap(variant &other) {
using std::swap;
swap(storage_, other.storage_);
swap(type_index_, other.type_index_);
}
private:
template <class T>
const T &get_ref() const {
typedef typename TypeAt<TL, IndexOf<TL, T>::value>::type value_type;
if(IndexOf<TL, T>::value != type_index_) {
throw std::bad_cast();
}
return *reinterpret_cast<const value_type *>(&storage_);
}
template <class T>
T &get_ref() {
typedef typename TypeAt<TL, IndexOf<TL, T>::value>::type value_type;
if(IndexOf<TL, T>::value != type_index_) {
throw std::bad_cast();
}
return *reinterpret_cast<value_type *>(&storage_);
}
template <class T>
const T *get_ptr() const {
typedef typename TypeAt<TL, IndexOf<TL, T>::value>::type value_type;
if(IndexOf<TL, T>::value != type_index_) {
return 0;
}
return reinterpret_cast<const value_type *>(&storage_);
}
template <class T>
T *get_ptr() {
typedef typename TypeAt<TL, IndexOf<TL, T>::value>::type value_type;
if(IndexOf<TL, T>::value != type_index_) {
return 0;
}
return reinterpret_cast<value_type *>(&storage_);
}
public:
int which() const {
return type_index_;
}
bool empty() const {
return false;
}
const std::type_info &type() const;
private:
struct { unsigned char buffer_[MaxSize<TL>::value]; } storage_;
int type_index_;
};
// accessors
template<class U, class TL>
U *get(variant<TL> *v) {
return v->template get_ptr<U>();
}
template<class U, class TL>
const U *get(const variant<TL> *v) {
return v->template get_ptr<U>();
}
template<class U, class TL>
U &get(variant<TL> &v) {
return v.template get_ref<U>();
}
template<class U, class TL>
const U &get(const variant<TL> &v) {
return v.template get_ref<U>();
}
}
#endif
そしてこれはとてもうまくいきます!次のようなものを書くことができ、うまく機能します。
typedef util::variant<TYPELIST_3(std::string, int, double)> variant;
variant x = std::string("hello world");
variant y = 10;
variant z = 123.45;
std::cout << util::get<std::string>(x) << std::endl;
std::cout << util::get<int>(y) << std::endl;
std::cout << util::get<double>(z) << std::endl;
そしてすべて期待通りに動作します:-)。ここで私の質問です。と boost::variant
以下のように問題なく書くことができます。
boost::variant<int, std::string> v = "hello world";
私のバージョンで同様に書くと、次のようになります。
util::variant<TYPELIST_2(int, std::string)> v = "hello world";
次のようなエラーが表示されます。
variant.hpp: In instantiation of 'util::<unnamed>::TypeAt<TypeList<std::basic_string<char>, NullType>, 4294967294u>':
variant.hpp:76:47: instantiated from 'util::<unnamed>::TypeAt<TypeList<int, TypeList<std::basic_string<char>, NullType> >, 4294967295u>'
variant.hpp:161:61: instantiated from 'util::variant<TL>::variant(const T&) [with T = char [12], TL = TypeList<int, TypeList<std::basic_string<char>, NullType> >]'
test.cc:27:50: instantiated from here
variant.hpp:76:47: error: invalid use of incomplete type 'struct util::<unnamed>::TypeAt<NullType, 4294967293u>'
variant.hpp:32:46: error: declaration of 'struct util::<unnamed>::TypeAt<NullType, 4294967293u>'
基本的には見つからない char[12]
バリアントのタイプリスト内。それは理にかなっています char[12]
実際には、タイプの 1 つとして明示的にリストされていません...
どうやって boost::variant
これをそんなにシームレスに動作させることができるのでしょうか? それが私の理解において本当に欠けている唯一の部分であるように感じます boost::variant
動作します。考えは?
解決
他の答えを提案したのでis_convertibleをしたくありません。基本的にC ++型の形質を使用してC ++変換メカニズムを再実装することになります。代わりに、すでに持っているC ++インフラストラクチャを使用できます。
ブーストは、バリアントが受け入れることができるすべてのタイプを取り込む関数をクラスにすることです。C ++ 03では正確にBoostがどのようにしているのかわかりませんが、C ++ 11構文:
template <typename First, typename... Rest>
class constructor : public constructor<Rest...>
{
using constructor<Rest...>::construct;
static void
construct(variant& v, First&& value);
};
.
それからあなたの演算子=および他の関数はconstructor<Types...>::construct(*this, value)
を呼び出し、明確な変換がある場合、C ++はあなたのためにそれを見つけます。
私は、このうちどんなどんな方法を解剖したかどうかを解剖しました。
他のヒント
次のような型の特性を利用できます。 is_convertible
(または C++11 stdlib バージョン)。
@Andreasのコメントにあるように、特定の型を検索するのではなく、最初の一致を検索するように、テンプレート化されたコンストラクター/代入演算子を少し修正する必要があります。
#include <boost/mpl/if.hpp>
#include <boost/type_traits/is_convertible.hpp>
template<class T, class TList>
struct FirstMatch;
template<class T, class Head, class Tail>
struct FirstMatch<T, TypeList<Head, Tail>>{
static bool const is_conv = boost::is_convertible<T, Head>::value;
typedef typename boost::mpl::if_c<is_conv, Head,
typename FirstMatch<T, Tail>::type>::type type;
};
template<class T>
struct FirstMatch<T, NullType>{
typedef struct ERROR_no_convertible_type_found type;
};
template<class T, class TList>
struct FirstOrExactMatch{
static int const idx = IndexOf<TList, T>::value;
typedef typename boost::mpl::if_c<idx != -1,
TypeAt<TList, idx>,
FirstMatch<T, TList>
>::type::type type;
};
コードはテストされていませんが、(タイプミスを除いて) 動作するはずです。