C ++ 11을 특별히 강조하면서 C ++ 학습의 일환으로 Boost의 Variant (위치 여기 ). 내 코드는 variant.hpp 에서 사용할 수 있으며 현재 버전은 아래와 같습니다. .
std::aligned_storage
을 어떻게 이식성있게 사용할 수 있습니까? 현재 내 솔루션은 아마도 static_cast
를 이식 불가능하게 사용합니다. 이식 가능한 경우 해당 정보는 매우 중요합니다. 특정 코드는 iv id = 유형의 value
에 대해 *static_cast<T*>(static_cast<void*>(&value))
와 유사합니다. “877d4f443a”>
(여기서...
는 가변 템플릿을 나타내지 않습니다.)
.이 특정 용도에서 SFINAE가 더 나을까요? SFINAE를 사용하여 실행 가능한 함수 집합에서 과부하를 제거 할 수 있다는 것을 알고 있지만 여기서는 static_assert
를 사용합니다. 거기에 가정 하나 이상의 실행 가능한 기능이있는 경우의 가치있는 예를 찾을 수 있지만 실행 가능한 기능은 하나뿐입니다.
std::forward
를 많이 사용했습니다. 더 적은 사용으로 얻을 수 있습니까?
생성자 오버로드 중 하나에서 std::enable_if
를 사용하여 이동시에만 사용되도록했습니다. 의도 된 것입니다 (variant(U&& value, typename detail::variant::enable_if_elem<U, T...>::type* = nullptr, typename detail::variant::enable_if_movable<U>::type* = nullptr)
참조). enable_if
둘 다없이이 생성자는 복사 생성자 variant(variant const&)
가 대신 의도 된 경우에 사용됩니다. 최종 컴파일러 오류. 이 동작을 강제하는 더 좋은 방법이 있습니까? 내가 시도한 한 가지 솔루션은 variant(variant const& rhs)
로 델 게이트되는 오버로드로 variant(variant&)
를 포함하는 것이 었습니다. , variant(U&&)
는 오버로드 규칙에서 variant(variant const&)
보다 선호됩니다. 범용 참조 대신 이동 의미 체계를 사용할 때 새로 도입 된 일부 T
에 대해 T&&
를 사용할 때 일반적인 모범 사례는 무엇입니까?
일반적인 경우 (가변 템플릿 사용) 일부 문제가 있지만 다중 방문자를 추가해야합니다. variant
클래스를 구현할 때 흥미로운 점은 템플릿 인수를 재 배열하거나 lvalue 템플릿이있는 위치 만 포함하는 variant
간의 암시 적 변환이었습니다. 인수는 rvalue 템플릿 인수의 상위 집합입니다.
모든 의견, 질문 또는 조언은 대단히 감사합니다.
#ifndef WART_VARIANT_HPP #define WART_VARIANT_HPP #include <type_traits> #include <utility> #include "math.hpp" namespace wart { template <typename... T> class variant; namespace detail { namespace variant { template <typename... T> using variant = wart::variant<T...>; template <typename T> using is_movable = typename std::integral_constant <bool, std::is_rvalue_reference<T&&>::value && !std::is_const<T>::value>; template <typename T, typename U = void> using enable_if_movable = std::enable_if<is_movable<T>::value, U>; template <typename... Types> using union_storage = typename std::aligned_storage <math::max_constant<std::size_t, sizeof(Types)...>::value, math::lcm_constant<std::size_t, std::alignment_of<Types>::value...>::value>::type; template <typename... Types> using union_storage_t = typename union_storage<Types...>::type; template <typename Elem, typename... List> struct elem; template <typename Head, typename... Tail> struct elem<Head, Head, Tail...>: std::true_type {}; template <typename Elem, typename Head, typename... Tail> struct elem<Elem, Head, Tail...>: elem<Elem, Tail...>::type {}; template <typename Elem> struct elem<Elem>: std::false_type {}; template <typename Elem, typename... List> struct elem_index; template <typename Head, typename... Tail> struct elem_index<Head, Head, Tail...>: std::integral_constant<int, 0> {}; template <typename Elem, typename Head, typename... Tail> struct elem_index<Elem, Head, Tail...>: std::integral_constant<int, elem_index<Elem, Tail...>::value + 1> {}; template <bool... List> struct all; template <> struct all<>: std::true_type {}; template <bool... Tail> struct all<true, Tail...>: all<Tail...>::type {}; template <bool... Tail> struct all<false, Tail...>: std::false_type {}; template <typename Elem, typename... List> using enable_if_elem = std::enable_if<elem<Elem, List...>::value>; template <typename F, typename... ArgTypes> using common_result_of = std::common_type<typename std::result_of<F(ArgTypes)>::type...>; struct destroy { template <typename T> void operator()(T&& value) { using type = typename std::remove_reference<T>::type; std::forward<T>(value).~type(); } }; struct copy_construct { void* storage; template <typename T> void operator()(T const& value) { new (storage) T(value); } }; template <typename... T> struct copy_construct_index { void* storage; template <typename U> int operator()(U const& value) { new (storage) U(value); return elem_index<U, T...>::value; } }; struct move_construct { void* storage; template <typename T> typename enable_if_movable<T>::type operator()(T&& value) { new (storage) T(std::move(value)); } }; template <typename... T> struct move_construct_index { void* storage; template <typename U> typename enable_if_movable<U, int>::type operator()(U&& value) { new (storage) U(std::move(value)); return elem_index<U, T...>::value; } }; struct copy_assign { void* storage; template <typename T> void operator()(T const& value) { *static_cast<T*>(storage) = value; } }; template <typename... T> struct copy_assign_reindex { variant<T...>& variant; template <typename U> void operator()(U const& value) { if (variant.which_ == elem_index<U, T...>::value) { *static_cast<U*>(static_cast<void*>(&variant.storage_)) = value; } else { variant.accept(destroy{}); new (&variant.storage_) U(value); variant.which_ = elem_index<U, T...>::value; } } }; struct move_assign { void* storage; template <typename T> typename enable_if_movable<T>::type operator()(T&& value) { *static_cast<T*>(storage) = std::move(value); } }; template <typename... T> struct move_assign_reindex { variant<T...>& variant; template <typename U> typename enable_if_movable<U>::type operator()(U&& value) { if (variant.which_ == elem_index<U, T...>::value) { *static_cast<U*>(static_cast<void*>(&variant.storage_)) = std::move(value); } else { variant.accept(destroy{}); new (&variant.storage_) U(std::move(value)); variant.which_ = elem_index<U, T...>::value; } } }; } } template <typename... T> class variant { int which_; detail::variant::union_storage_t<T...> storage_; public: template <typename F> using result_of = detail::variant::common_result_of<F, T...>; template <typename F> using result_of_t = typename result_of<F>::type; template <typename U> variant(U const& value, typename detail::variant::enable_if_elem<U, T...>::type* = nullptr): which_{detail::variant::elem_index<U, T...>::value} { new (&storage_) U(value); } template <typename U> variant(U&& value, typename detail::variant::enable_if_elem<U, T...>::type* = nullptr, typename detail::variant::enable_if_movable<U>::type* = nullptr): which_{detail::variant::elem_index<U, T...>::value} { new (&storage_) U(std::move(value)); } variant(variant const& rhs): which_{rhs.which_} { rhs.accept(detail::variant::copy_construct{&storage_}); } template <typename... U> variant(variant<U...> const& rhs, typename std::enable_if< detail::variant::all<detail::variant::elem<U, T...>::value...>::value >::type* = nullptr): which_{rhs.accept(detail::variant::copy_construct_index<T...>{&storage_})} {} variant(variant&& rhs): which_{rhs.which_} { std::move(rhs).accept(detail::variant::move_construct{&storage_}); } template <typename... U> variant(variant<U...>&& rhs, typename std::enable_if< detail::variant::all<detail::variant::elem<U, T...>::value...>::value >::type* = nullptr): which_{std::move(rhs).accept(detail::variant::move_construct_index<T...>{&storage_})} {} ~variant() { accept(detail::variant::destroy{}); } variant& operator=(variant const& rhs) & { using namespace detail::variant; static_assert(all<std::is_nothrow_copy_constructible<T>::value...>::value, "all template arguments T must be nothrow copy constructible in class template variant"); if (this == &rhs) { return *this; } if (which_ == rhs.which_) { rhs.accept(copy_assign{&storage_}); } else { accept(destroy{}); rhs.accept(copy_construct{&storage_}); which_ = rhs.which_; } return *this; } template <typename... U> variant& operator=(variant<U...> const& rhs) & { using namespace detail::variant; static_assert(all<std::is_nothrow_copy_constructible<T>::value...>::value, "all template arguments T must be nothrow copy constructible in class template variant"); rhs.accept(copy_assign_reindex<T...>{*this}); return *this; } variant& operator=(variant&& rhs) & { using namespace detail::variant; static_assert(all<std::is_nothrow_move_constructible<T>::value...>::value, "all template arguments T must be nothrow move constructible in class template variant"); if (this == &rhs) { return *this; } if (which_ == rhs.which_) { std::move(rhs).accept(move_assign{&storage_}); } else { accept(detail::variant::destroy{}); std::move(rhs).accept(move_construct{&storage_}); which_ = rhs.which_; } return *this; } template <typename... U> variant& operator=(variant<U...>&& rhs) & { using namespace detail::variant; static_assert(all<std::is_nothrow_copy_constructible<T>::value...>::value, "all template arguments T must be nothrow copy constructible in class template variant"); std::move(rhs).accept(move_assign_reindex<T...>{*this}); return *this; } template <typename F> result_of_t<F> accept(F&& f) const& { using namespace detail::variant; using call = result_of_t<F&&> (*)(F&& f, union_storage_t<T...> const&); static call calls[] { [](F&& f, union_storage_t<T...> const& value) { return std::forward<F>(f)(*static_cast<T const*>(static_cast<void const*>(&value))); }... }; return calls[which_](std::forward<F>(f), storage_); } template <typename F> result_of_t<F> accept(F&& f) & { using namespace detail::variant; using call = result_of_t<F&&> (*)(F&& f, union_storage_t<T...>&); static call calls[] { [](F&& f, union_storage_t<T...>& value) { return std::forward<F>(f)(*static_cast<T*>(static_cast<void*>(&value))); }... }; return calls[which_](std::forward<F>(f), storage_); } template <typename F> result_of_t<F> accept(F&& f) && { using namespace detail::variant; using call = result_of_t<F> (*)(F&& f, union_storage_t<T...>&&); static call calls[] { [](F&& f, union_storage_t<T...>&& value) { return std::forward<F>(f)(std::move(*static_cast<T*>(static_cast<void*>(&value)))); }... }; return calls[which_](std::forward<F>(f), std::move(storage_)); } friend struct detail::variant::copy_assign_reindex<T...>; friend struct detail::variant::move_assign_reindex<T...>; }; } #endif
의견
답변
여기에는 많은 것이 있으므로 리뷰를 여러 부분으로 나눌 것입니다. 메타 함수 섹션에 집중하여 시작하고 싶습니다. 메타 함수는 짧을 수 있지만 “정확성 및 유용성 측면에서 매우 강력하고 중요합니다.
시작하려면 다음과 같이하십시오.
template <typename T> using is_movable = typename std::integral_constant <bool, std::is_rvalue_reference<T&&>::value && !std::is_const<T>::value>; template <typename T, typename U = void> using enable_if_movable = std::enable_if<is_movable<T>::value, U>;
첫 번째는 단순히 잘못되었습니다. “이 메타 함수를 사용하여 유형이 이동 구성 가능한지 확인합니다 (move_construct
에서) … lvalue 참조 나 const
가 아닌지 확인하면됩니다. 실제로 이동 생성과 관련된 어떤 것도 확인하지 않습니다. 어떤 것이 rvalue 참조라고해서 그로부터 이동할 수 있다는 의미는 아닙니다. lvalue 참조라고해서 그렇게 할 수 없다는 의미는 아닙니다. 두 가지 간단한 클래스를 고려하십시오. / p>
struct No { A(A&& ) = delete; }; struct Yes { };
이름에서 알 수 있듯이 No
는 이동을 구성 할 수 없습니다. 메타 함수에 따르면 그렇습니다. 또한 Yes&
는 이동 구성이 가능하지만 메타 함수는 아니오라고 표시합니다.
올바른 구현은 단순히 표준 유형 특성 를 사용하는 것입니다. std::is_move_constructible
.
둘째, 별칭이 의심 스럽습니다. 일반적으로 별칭을 사용하여 다음과 같은 작업을 피 합니다. typename ::type
내용을 작성하세요. 당신은 그렇게하지 않고, 그 결과 호출은 그다지 간결하지 않습니다. 비교 :
typename enable_if_movable<T>::type // yours with alias std::enable_if_t<is_moveable<T>::value> // just using enable_if without alias std::enable_if_t<std::is_move_constructible<T>::value> // just stds
개인적으로 마지막 버전을 선호합니다. 여기서는 C ++ 14 별칭을 사용하고 있습니다. C ++ 14 컴파일러가없는 경우 모두 메타 함수 라이브러리를 시작하는 것이 절대적으로 가치가 있습니다. 다음과 같이 작성하면됩니다.
template <bool B, typename T = void> using enable_if_t = typename std::enable_if<B, T>::type;
이동 :
template <typename Elem, typename... List> struct elem;
누구나 여기서 elem
가 무엇을하는지 알 수 있습니다. 구현을 읽을 때까지 읽지 않았습니다. 이에 대한 훨씬 더 나은 이름은 contains
입니다. 그러나 잠시 후에 구현으로 돌아갑니다.
먼저 다음으로 시작하겠습니다.
template <bool... List> struct all;
all
는 매우 유용합니다. 가까운 친척도 마찬가지입니다. any
및 none
. all
를 작성하는 방식은 훌륭하고 작동하지만 만들지 않습니다. 다른 두 개를 작성하는 것이 더 쉽습니다. 이를 작성하는 좋은 방법은 @Columbo “의 bool_pack
트릭을 사용하는 것입니다.
template <bool...> struct bool_pack; template <bool f, bool... bs> using all_same = std::is_same<bool_pack<f, bs...>, bool_pack<bs..., f>>;
그것입니다. 당신의 도우미입니다. 이를 사용하여 나머지는 모두 쉽게 구현할 수 있습니다.
template <bool... bs> using all_of = all_same<true, bs...>; template <bool... bs> using none_of = all_same<false, bs...>; template <bool... bs> using any_of = std::integral_constant<bool, !none_of<bs...>::value>;
그런 다음 contains
한 줄로 :
template <typename Elem, typename... List> using contains = any_of<std::is_same<Elem, List>::value...>;
이전과 마찬가지로 enable_if_elem
에 값이 표시되지 않습니다. . 그리고 common_result_of
는 메타 함수를 산출하는 것이 아니라 유형 을 가져야합니다.
template <typename F, typename... ArgTypes> using common_result_of = std::common_type_t<std::result_of_t<F(ArgTypes)>::...>;
variant
자체에 그대로 두는 것이 더 가독성이 높지만 :
// no need to use anything in detail::, unless you need to // write your own aliases for common_type_t and result_of_t template <typename F> using result_of = std::common_type_t<std::result_of_t<F(T)>...>;
이제 사용법에 대해 알아보세요. . 전반적으로 반환 유형에서 다음과 같은 메타 함수를 사용합니다.
template <typename T> typename enable_if_movable<T>::type operator()(T&& value);
또는 더미 포인터 :
template <typename U> variant(U const& value, typename detail::variant::enable_if_elem<U, T...>::type* = nullptr)
하지만 두 경우 모두 SFINAE 로직을 이름이 지정되지 않은 최종 템플릿 매개 변수로 입력하면 복잡한 템플릿 표현식을 구문 분석하는 것이 훨씬 더 쉽다는 것을 알았습니다.
template <typename T, typename = std::enable_if_t<std::is_move_constructible<T>::value> > void operator()(T&& value); template <typename U, typename = std::enable_if_t<contains<U, T...>::value> > variant(U const& value);
일관성은 이해에 도움이됩니다. 더미 포인터 인수는 C ++ 03에서 남은 혼란스러운 해킹입니다. 더 이상 필요하지 않습니다. 특히 두 더미 포인터가 필요한 경우 :
template <typename U, typename = std::enable_if_t<contains<U, T...>::value && std::is_move_constructible<U>::value> > variant(U&& value);
이 사람에 대한 사이드 노트. 실제로 원하는 것을하지 않습니다. 이것은 “임의의 rvalue 참조가 아니라 전달 참조”입니다. 실제로 여기에서 두 생성자를 한 번에 결합 할 수도 있습니다.
template <typename U, typename V = std::remove_reference_t<U>, typename = std::enable_if_t<contains<V, T...>::value && std::is_constructible<V, U&&>::value> > variant(U&& value) : which_{elem_index<V, T...>::value} { new (&storage_) V(std::forward<U>(value)); }
이것은 코드의 또 다른 문제를 해결합니다. 클래스가 복사 구성 가능한 경우. 변형에 unique_ptr
와 같은 것을 붙이고 싶다면 어떨까요? 구성 가능하게 이동하지만 복사는 불가능합니다.하지만 코드에서 확인한 적이 한 번도 없습니다. 중요-그렇지 않으면 사용자가 알 수없는 오류 메시지를 받게 될 것입니다.
이것이 메타 함수 부분을 마친 것 같습니다. 나중에 variant
자체에 대한 리뷰를 작성하겠습니다.도움이 되었기를 바랍니다.
std::aligned_storage
를 사용하는 방식 때문입니다. 관련type
의 휴대용 사용에 대한 조언은 매우 도움이 될 것입니다. ” 일 “에 의해 ” 원래 의도를 충족합니까? “, 대답은 예입니다. 문체와 모범 사례에 대한 조언을 아주 좋아합니다.std::forward
는 괜찮아 보이지만 ‘ 사용할 수없는 곳은 볼 수 없습니다. 더 나은 오류 메시지를 제공 할 수있는static_assert
를 선호합니다. 모든 포인터가 동일한 경계에 정렬된다는 보장이 없기 때문에 이식성은 잠재적으로 문제가 될 수 있습니다 (따라서void *
및T*
는 요구 사항이 다를 수 있음) ), 그러나 이것은 요즘 매우 드문 일입니다. 저는 ‘ 부스트가이 문제를 어떻게 해결하는지, 아니면 그냥 무시하는지 모르겠습니다.std::tuple
가 사용하는 것과 유사한 기술을 사용하지만union
를 사용합니다.is_movable
및) 다른 곳에 살 수 있습니다. 안타깝게도 여기에 좋은 피드백을 제공하는 데 필요한 지식을 갖춘 C ++ 리뷰어의 수는 코드가 상당히 복잡하기 때문에 아마도 한 자릿수 일 것입니다.