creation of choice/variant type

This commit is contained in:
Francisco Paisana 2020-03-29 17:56:50 +01:00 committed by Francisco Paisana
parent a2ade9edd1
commit 278a1686fb
3 changed files with 527 additions and 5 deletions

View File

@ -0,0 +1,345 @@
/*
* Copyright 2013-2020 Software Radio Systems Limited
*
* This file is part of srsLTE.
*
* srsLTE is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* srsLTE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* A copy of the GNU Affero General Public License can be found in
* the LICENSE file in the top-level directory of this distribution
* and at http://www.gnu.org/licenses/.
*
*/
#include <limits>
#include <memory>
#include <type_traits>
#ifndef SRSLTE_CHOICE_TYPE_H
#define SRSLTE_CHOICE_TYPE_H
namespace srslte {
namespace choice_details {
using size_idx_t = std::size_t;
static constexpr size_idx_t invalid_idx = std::numeric_limits<size_idx_t>::max();
class bad_choice_access : public std::runtime_error
{
public:
explicit bad_choice_access(const std::string& what_arg) : runtime_error(what_arg) {}
explicit bad_choice_access(const char* what_arg) : runtime_error(what_arg) {}
};
//! Get Index of a type in a list of types (reversed)
template <typename T, typename... Types>
struct type_indexer;
template <typename T, typename First, typename... Types>
struct type_indexer<T, First, Types...> {
static constexpr size_idx_t index =
std::is_same<T, First>::value ? sizeof...(Types) : type_indexer<T, Types...>::index;
};
template <typename T>
struct type_indexer<T> {
static constexpr size_idx_t index = invalid_idx;
};
//! Get a type of an index in a list of types
template <std::size_t I, typename... Types>
struct type_get;
template <std::size_t I, typename First, typename... Types>
struct type_get<I, First, Types...> {
using type = typename std::conditional<I == sizeof...(Types), First, typename type_get<I, Types...>::type>::type;
};
template <std::size_t I>
struct type_get<I> {
using type = void;
};
//! Compute maximum at compile time
template <std::size_t arg1, std::size_t... others>
struct static_max;
template <std::size_t arg>
struct static_max<arg> {
static const std::size_t value = arg;
};
template <std::size_t arg1, std::size_t arg2, std::size_t... others>
struct static_max<arg1, arg2, others...> {
static const std::size_t value =
arg1 >= arg2 ? static_max<arg1, others...>::value : static_max<arg2, others...>::value;
};
//! Holds one of the Args types
template <std::size_t MaxSize, std::size_t MaxAlign>
struct choice_storage_t {
static const std::size_t max_size = MaxSize;
static const std::size_t max_align = MaxAlign;
using buffer_t = typename std::aligned_storage<max_size, max_align>::type;
buffer_t buffer;
void* get_buffer() { return &buffer; }
template <typename T>
T& get_unsafe()
{
return *reinterpret_cast<T*>(&buffer);
}
template <typename T>
const T& get_unsafe() const
{
return *reinterpret_cast<const T*>(&buffer);
}
template <typename U>
void destroy_unsafe()
{
get_unsafe<U>().~U();
};
};
/*************************
* Tagged Union Helpers
************************/
template <typename C>
struct CopyCtorVisitor {
explicit CopyCtorVisitor(C* c_) : c(c_) {}
template <typename T>
void operator()(const T& t)
{
c->construct_unsafe(t);
}
C* c;
};
template <typename C>
struct MoveCtorVisitor {
explicit MoveCtorVisitor(C* c_) : c(c_) {}
template <typename T>
void operator()(T&& t)
{
c->construct_unsafe(std::move(t));
}
C* c;
};
template <typename C>
struct DtorUnsafeVisitor {
explicit DtorUnsafeVisitor(C* c_) : c(c_) {}
template <typename T>
void operator()(T& t)
{
c->template destroy_unsafe<T>();
}
C* c;
};
/**
* @brief visit pattern implementation
* @tparam F functor
* @tparam V tagged union type
* @tparam Types remaining types to iterate
*/
template <typename F, typename V, typename... Types>
struct visit_impl;
template <typename F, typename V, typename First, typename... Types>
struct visit_impl<F, V, First, Types...> {
static void apply(V& c, F&& f)
{
if (c.template is<First>()) {
f(c.template get_unsafe<First>());
} else {
visit_impl<F, V, Types...>::apply(c, std::forward<F>(f));
}
}
static void apply(const V& c, F&& f)
{
if (c.template is<First>()) {
f(c.template get_unsafe<First>());
} else {
visit_impl<F, V, Types...>::apply(c, std::forward<F>(f));
}
}
};
template <typename F, typename V, typename T>
struct visit_impl<F, V, T> {
static void apply(V& c, F&& f) { f(c.template get_unsafe<T>()); }
static void apply(const V& c, F&& f) { f(c.template get_unsafe<T>()); }
};
template <typename... Args>
struct tagged_union_t;
template <typename Functor, typename First, typename... Args>
void visit(tagged_union_t<First, Args...>& u, Functor&& f)
{
visit_impl<Functor, tagged_union_t<First, Args...>, First, Args...>::apply(u, std::forward<Functor>(f));
}
template <typename Functor, typename First, typename... Args>
void visit(const tagged_union_t<First, Args...>& u, Functor&& f)
{
visit_impl<Functor, tagged_union_t<First, Args...>, First, Args...>::apply(u, std::forward<Functor>(f));
}
template <typename... Args>
struct tagged_union_t
: private choice_storage_t<static_max<sizeof(Args)...>::value, static_max<alignof(Args)...>::value> {
using base_t = choice_storage_t<static_max<sizeof(Args)...>::value, static_max<alignof(Args)...>::value>;
using buffer_t = typename base_t::buffer_t;
using this_type = tagged_union_t<Args...>;
using default_type = typename type_get<sizeof...(Args) - 1, Args...>::type;
std::size_t type_id;
using base_t::destroy_unsafe;
using base_t::get_buffer;
using base_t::get_unsafe;
auto construct_unsafe() -> decltype(default_type(), void())
{
type_id = sizeof...(Args) - 1;
new (get_buffer()) default_type();
}
template <typename U>
void construct_unsafe(U&& u)
{
using U2 = typename std::decay<U>::type;
static_assert(type_indexer<U2, Args...>::index != invalid_idx,
"The provided type to ctor is not part of the list of possible types");
type_id = type_indexer<U2, Args...>::index;
new (get_buffer()) U2(std::forward<U>(u));
}
void copy_unsafe(const this_type& other) { visit(other, CopyCtorVisitor<this_type>{this}); }
void move_unsafe(this_type&& other) { visit(other, MoveCtorVisitor<this_type>{this}); }
void dtor_unsafe() { visit(*this, choice_details::DtorUnsafeVisitor<base_t>{this}); }
template <std::size_t I, typename T = typename type_get<sizeof...(Args) - I - 1, Args...>::type>
T& get_unsafe()
{
return get_unsafe<T>();
}
template <typename T>
bool is() const
{
return type_indexer<T, Args...>::index == type_id;
}
template <typename T>
T& get()
{
if (is<T>()) {
return get_unsafe<T>();
}
throw choice_details::bad_choice_access{"in get<T>"};
}
template <typename T>
T* get_if()
{
return (is<T>()) ? &get_unsafe<T>() : nullptr;
}
template <typename T>
constexpr static bool can_hold_type()
{
return type_indexer<T, Args...>::index != invalid_idx;
}
};
} // namespace choice_details
template <typename... Args>
class choice_t : private choice_details::tagged_union_t<Args...>
{
using base_t = choice_details::tagged_union_t<Args...>;
template <typename T>
using enable_if_can_hold =
typename std::enable_if<base_t::template can_hold_type<typename std::decay<T>::type>()>::type;
public:
using base_t::can_hold_type;
using base_t::get;
using base_t::get_if;
using base_t::is;
choice_t() noexcept { this_union().construct_unsafe(); }
choice_t(const choice_t<Args...>& other) noexcept { base_t::copy_unsafe(other); }
choice_t(choice_t<Args...>&& other) noexcept { base_t::move_unsafe(std::move(other)); }
template <typename U, typename = enable_if_can_hold<U> >
explicit choice_t(U&& u) noexcept
{
base_t::construct_unsafe(std::forward<U>(u));
}
~choice_t() { base_t::dtor_unsafe(); }
template <typename U, typename = enable_if_can_hold<U> >
choice_t& operator=(U&& u) noexcept
{
if (not base_t::template is<U>()) {
base_t::dtor_unsafe();
}
base_t::construct_unsafe(std::forward<U>(u));
return *this;
}
template <typename U>
void emplace(U&& u) noexcept
{
*this = std::forward<U>(u);
}
choice_t& operator=(const choice_t& other) noexcept
{
if (this != &other) {
base_t::dtor_unsafe();
base_t::copy_unsafe(other);
}
return *this;
}
choice_t& operator=(choice_t&& other) noexcept
{
base_t::dtor_unsafe();
base_t::move_unsafe(other);
return *this;
}
private:
base_t& this_union() { return *this; }
const base_t& this_union() const { return *this; }
};
// template <typename Functor, typename First, typename... Args>
// void visit(choice_t<First, Args...>& u, Functor&& f)
//{
// choice_details::visit_impl<Functor, decltype(u), First, Args...>::template apply(u, f);
//}
} // namespace srslte
#endif // SRSLTE_CHOICE_TYPE_H

View File

@ -68,10 +68,10 @@ target_link_libraries(pdu_test srslte_phy srslte_common ${CMAKE_THREAD_LIBS_INIT
add_test(pdu_test pdu_test)
if (ENABLE_5GNR)
add_executable(mac_nr_pdu_test mac_nr_pdu_test.cc)
target_link_libraries(mac_nr_pdu_test srslte_phy srslte_common ${CMAKE_THREAD_LIBS_INIT})
add_test(mac_nr_pdu_test mac_nr_pdu_test)
endif(ENABLE_5GNR)
add_executable(mac_nr_pdu_test mac_nr_pdu_test.cc)
target_link_libraries(mac_nr_pdu_test srslte_phy srslte_common ${CMAKE_THREAD_LIBS_INIT})
add_test(mac_nr_pdu_test mac_nr_pdu_test)
endif (ENABLE_5GNR)
add_executable(stack_procedure_test stack_procedure_test.cc)
add_test(stack_procedure_test stack_procedure_test)
@ -94,4 +94,8 @@ add_test(test_common_test test_common_test)
add_executable(tti_point_test tti_point_test.cc)
target_link_libraries(tti_point_test srslte_common)
add_test(tti_point_test tti_point_test)
add_test(tti_point_test tti_point_test)
add_executable(choice_type_test choice_type_test.cc)
target_link_libraries(choice_type_test srslte_common)
add_test(choice_type_test choice_type_test)

View File

@ -0,0 +1,173 @@
/*
* Copyright 2013-2020 Software Radio Systems Limited
*
* This file is part of srsLTE.
*
* srsLTE is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* srsLTE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* A copy of the GNU Affero General Public License can be found in
* the LICENSE file in the top-level directory of this distribution
* and at http://www.gnu.org/licenses/.
*
*/
#include "srslte/common/choice_type.h"
#include "srslte/common/test_common.h"
namespace srslte {
namespace choice_details {
static_assert(static_max<1, 2>::value == 2, "StaticMax not working");
static_assert(static_max<2, 1>::value == 2, "StaticMax not working");
static_assert(type_indexer<double, char, float, int, long, double>::index == 0, "Type indexer not working");
static_assert(type_indexer<double, double, float, int, long, char>::index == 4, "Type indexer not working");
static_assert(type_indexer<double, char, float, int>::index == invalid_idx, "Type Indexer not working");
static_assert(sizeof(choice_storage_t<5, 4>) == 8, "Size of storage wrongly computed");
static_assert(alignof(choice_storage_t<5, 4>) == 4, "Alignment of storage wrongly computed");
static_assert(std::is_same<typename type_get<0, char, float, int, long, double>::type, double>::value,
"type index-based search not working");
static_assert(std::is_same<typename type_get<3, char, float, int, long, double>::type, float>::value,
"type index-based search not working");
static_assert(std::is_same<tagged_union_t<char, int, double>::default_type, char>::value,
"Default type is incorrect\n");
static_assert(tagged_union_t<char, int, double>::can_hold_type<int>(), "Can hold type implementation is incorrect\n");
static_assert(not tagged_union_t<char, int, double>::can_hold_type<uint8_t>(),
"Can hold type implementation is incorrect\n");
struct C {
static int counter;
C() { counter++; }
C(C&&) { counter++; }
C(const C&) { counter++; }
C& operator=(C&& other)
{
counter++;
return *this;
}
C& operator=(const C& other)
{
if (this != &other) {
counter++;
}
return *this;
}
~C() { counter--; }
};
int C::counter = 0;
struct D {
static int counter;
D() { counter++; }
D(D&&) { counter++; }
D(const D&) = delete;
D& operator=(const D&) = delete;
D& operator==(D&&)
{
counter++;
return *this;
}
~D() { counter--; }
};
int D::counter = 0;
int test_tagged_union()
{
tagged_union_t<char, int, double, C> u;
u.construct_unsafe(5);
TESTASSERT(u.is<int>());
TESTASSERT(u.get_unsafe<int>() == 5);
TESTASSERT(u.get_unsafe<1>() == 5);
u.destroy_unsafe<int>();
TESTASSERT(C::counter == 0);
u.construct_unsafe<C>(C{});
TESTASSERT(C::counter == 1);
u.destroy_unsafe<C>();
TESTASSERT(C::counter == 0);
return SRSLTE_SUCCESS;
}
int test_choice()
{
TESTASSERT(C::counter == 0);
TESTASSERT(D::counter == 0);
{
int i = 6;
C c0{};
// TEST: correct construction, holding the right type and value
choice_t<char, int, double, C> c, c2{i}, c3{c0};
TESTASSERT(c.is<char>());
TESTASSERT(c2.is<int>() and c2.get<int>() == i and *c2.get_if<int>() == i);
TESTASSERT(c3.is<C>());
TESTASSERT(C::counter == 2);
// TEST: Invalid member access. get<>() should throw
TESTASSERT(c2.get_if<char>() == nullptr);
bool catched = false;
try {
char n = '1';
n = c2.get<char>();
TESTASSERT(n == '1');
} catch (choice_details::bad_choice_access& e) {
catched = true;
}
TESTASSERT(catched);
// TEST: simple emplace after construction
c2 = 'c';
TESTASSERT(c2.is<char>() and c2.get<char>() == 'c');
// TEST: copy ctor test.
choice_t<char, int, double, C> c5{c3};
TESTASSERT(C::counter == 3);
TESTASSERT(c5.is<C>());
TESTASSERT(c5.get_if<C>() == &c5.get<C>());
// TEST: copy assignment
c = c5;
TESTASSERT(C::counter == 4);
TESTASSERT(c.is<C>() and c.get_if<C>() != c5.get_if<C>());
c = c2;
TESTASSERT(C::counter == 3);
TESTASSERT(c2.is<char>() and c.get<char>() == 'c');
}
TESTASSERT(C::counter == 0);
TESTASSERT(D::counter == 0);
{
choice_t<char, int, double, C, D> c, c2{5.0}, c3{C{}}, c4{D{}};
TESTASSERT(c.is<char>());
TESTASSERT(c2.is<double>() and c2.get<double>() == 5.0 and *c2.get_if<double>() == 5.0);
TESTASSERT(c3.is<C>());
TESTASSERT(c4.is<D>());
TESTASSERT(C::counter == 1);
TESTASSERT(D::counter == 1);
choice_t<char, int, double, C, D> c5{std::move(c3)};
TESTASSERT(C::counter == 2);
choice_t<char, int, double, C, D> c6{std::move(c4)};
TESTASSERT(D::counter == 2);
}
TESTASSERT(C::counter == 0);
TESTASSERT(D::counter == 0);
return SRSLTE_SUCCESS;
}
} // namespace choice_details
} // namespace srslte
int main()
{
TESTASSERT(srslte::choice_details::test_tagged_union() == SRSLTE_SUCCESS);
TESTASSERT(srslte::choice_details::test_choice() == SRSLTE_SUCCESS);
}