Bon... j'ai passé la matinée sur un truc rien que pour vous.
C'est pas forcément super utile, mais je suis grave content de moi.
D'abord la partie "générique" qui devrait marcher avec n'importe quel enum. C'est poilu, c'est à base de Boost.MPL et de variadic templates :
http://codepad.org/RIhpDi1W
Code:
#include <boost/mpl/vector_c.hpp>
#include <boost/mpl/range_c.hpp>
#include <boost/mpl/find.hpp>
#include <boost/mpl/transform.hpp>
#include <boost/mpl/size.hpp>
#include <boost/mpl/unique.hpp>
#include <boost/mpl/sort.hpp>
#include <boost/mpl/front.hpp>
#include <boost/mpl/back.hpp>
#include <boost/mpl/at.hpp>
#include <array>
#include <functional>
#include <iostream>
namespace enums
{
namespace bm = boost::mpl;
namespace sequence
{
// the std::array type corresponding to the provided MPL sequence
template< typename S > using array_type = std::array< typename bm::front< S >::type::value_type, bm::size< S >::type::value >;
namespace detail
{
// recursive variadic template trick to build a sequence of indices
template< size_t... Indices > struct indices { using next = indices< Indices..., sizeof... (Indices) >; };
template< size_t Size > struct make_indices { using type = typename make_indices< Size - 1 >::type::next; };
template< > struct make_indices< 0 > { using type = indices< >; };
template< typename S, size_t... Indices >
constexpr array_type< S > to_array(indices< Indices... > const& i)
{ return {{ bm::at< S, bm::int_< Indices > >::type::value... }}; }
}
// simplify syntax
template< typename S > using make_indices = typename detail::make_indices< bm::size< S >::type::value >::type;
// automatically build the indices from the sequence and return the array
template< typename S >
constexpr array_type< S > to_array()
{ return detail::to_array< S >(make_indices< S >()); }
}
// specialize this getter to make your enum values available to all the other structures
template< typename E >
struct get_values { typedef bm::vector< > type; };
template< typename E > using values_of = typename get_values< E >::type;
// transformer that will replace values that are in the provided sequence by their index, or by 1 past the end of the sequence if the value is not found.
template< typename S >
struct make_index
{
using value_type = typename bm::front< S >::type::value_type;
template< size_t V > using key = bm::integral_c< value_type, value_type(V) >;
template< size_t V > using index = bm::integral_c< size_t, size_t(V) >;
template< typename T > struct apply {
using iterator = typename bm::find< S, key< T::value > >::type;
using not_found = index< bm::size< S >::type::value >;
using position = index< iterator::pos::value >;
typedef typename bm::if_< boost::is_same< iterator, bm::end< S > >, not_found, position >::type type;
};
};
// structure that will be in charge of creating arrays of enum values as well as an optimized table of enum<->index mapping
template< typename E, typename S = values_of< E > >
struct indexer {
struct detail {
// sort and make the values unique
using values = typename bm::unique< typename bm::sort< S >::type, bm::equal_to< bm::_1, bm::_2 > >::type;
static constexpr size_t begin = bm::front< values >::type::value;
static constexpr size_t end = bm::back< values >::type::value + 2;
static constexpr size_t size = end - begin;
// create a range for all the possible values
using range = bm::range_c< size_t, begin, end >;
using index_source = typename bm::copy< range, bm::back_inserter< bm::vector_c< size_t > > >::type;
// then build the index, mapping the enum value to its index in the array
using index = typename bm::transform< index_source, make_index< values > >::type;
};
using values_type = typename sequence::array_type< typename detail::values >;
using index_type = typename sequence::array_type< typename detail::index >;
static constexpr values_type values = sequence::to_array< typename indexer< E, S >::detail::values >();
static constexpr index_type index = sequence::to_array< typename indexer< E, S >::detail::index >();
};
// utility function to extend std::function features with an "not implemented" default function.
template< typename R, typename... Ts >
struct signature : std::function< R(Ts...) > {
signature(R(f)(Ts...)) : std::function< R(Ts...) >(f) {}
static R not_implemented(Ts... a) { std::cout << "Not implemented." << std::endl; return R(); }
};
// specialize this getter to make your enum handler signature available to all the other structures
template< typename E >
struct get_signature { typedef signature< void > type; };
template< typename E > using signature_of = typename get_signature< E >::type;
// specialize this getter to make your enum default handler available to all the other structures
template< typename E >
struct get_default { static signature_of< E > const& value; };
// default' default implementation is to call the corresponding signature's not_implemented function
template< typename E >
signature_of< E > const& get_default< E >::value = signature_of< E >::not_implemented;
// specialize this getter to bind your enum values to their handlers
template< typename E, E C >
struct get_handler { struct type { using value_type = signature_of< E >; static signature_of< E > const& value; }; };
// default' handler for any unbound value is the get_default< E > handler
template< typename E, E C >
signature_of< E > const& get_handler< E, C >::type::value = get_default< E >::value;
// transformer that will build a sequence of the handlers bound to each enum values
template< typename E, typename S >
struct make_dispatcher
{
using value_type = typename bm::front< S >::type::value_type;
template< typename T > struct apply { typedef typename get_handler< E, value_type(T::value) >::type type; };
};
// structure that will be in charge of providing handlers array and ways to access the correct handler from an enum value
template< typename E, typename S = values_of< E > >
struct dispatcher {
struct detail {
typedef typename bm::front< S >::type::value_type value_type;
using enum_indexer = indexer< E, S >;
using enum_handlers = typename bm::transform< typename indexer< E, S >::detail::values, make_dispatcher< E, S > >::type;
// add a default handler at the end of the sequence for any unknown enum values
using handlers = typename bm::push_back< enum_handlers, typename get_handler< E, E(enum_indexer::detail::size) >::type >::type;
// clamp the index so it is limited to actual handlers or to the default one at the end of the sequence, also use the offset introduced in the index table
static size_t constexpr clamp(size_t const i) { return enum_indexer::index[std::min(i - enum_indexer::detail::begin, 0 + enum_indexer::detail::size - 1)]; }
};
using handlers_type = typename sequence::array_type< typename detail::handlers >;
static const handlers_type handlers;
// accessor that will do all the operations to map an enum value to an handler
inline signature_of< E > const& operator[](typename detail::value_type const e) { return dispatcher::handlers[detail::clamp(e)]; }
};
template< typename E, typename S >
const typename dispatcher< E, S >::handlers_type dispatcher< E, S >::handlers = sequence::to_array< typename dispatcher< E, S >::detail::handlers >();
}
Ensuite la partie use-case avec l'enum en question de comment mapper tel opcode à tel handler :
http://codepad.org/5OxQ2e6b
Code:
enum op_code
{
DO_OPERATION_1 = 6,
DO_OPERATION_2 = 12,
DO_OPERATION_6 = 12,
DO_OPERATION_BLA = 7,
DO_OPERATION_UNBOUND = 8,
};
namespace enums
{
// declaration of the enum values in an MPL vector
template<> struct get_values< op_code > { typedef boost::mpl::vector_c< op_code, DO_OPERATION_1, DO_OPERATION_2, DO_OPERATION_BLA, DO_OPERATION_UNBOUND > type; };
// declaration of the handlers signatures for our enum
template<> struct get_signature< op_code > { typedef signature< void, std::string const&, std::string const& > type; };
}
// declaration of the handlers and binding to the dispatcher
void do_opcode_1(std::string const& param1, std::string const& param2) { std::cout << "doing opcode 1" << std::endl; }
template<> enums::signature_of< op_code > const& enums::get_handler< op_code, DO_OPERATION_1 >::type::value = do_opcode_1;
void do_opcode_2(std::string const& param1, std::string const& param2) { std::cout << "doing opcode 2" << std::endl; }
template<> enums::signature_of< op_code > const& enums::get_handler< op_code, DO_OPERATION_2 >::type::value = do_opcode_2;
void do_opcode_bla(std::string const& param1, std::string const& param2) { std::cout << "doing opcode bla" << std::endl; }
template<> enums::signature_of< op_code > const& enums::get_handler< op_code, DO_OPERATION_BLA >::type::value = do_opcode_bla;
int main(int argc, char const *argv[])
{
typedef enums::indexer< op_code > op_code_indexer;
typedef enums::dispatcher< op_code > op_code_dispatcher;
{
std::cout << "indexer::values array:" << std::endl;
size_t i = 0;
op_code_indexer::values_type constexpr values = op_code_indexer::values;
for(auto const& e : values) {
std::cout << " " << i << ": " << e << std::endl;
i++;
}
}
{
std::cout << "indexer::index array:" << std::endl;
size_t i = op_code_indexer::detail::begin;
op_code_indexer::index_type constexpr index = op_code_indexer::index;
for(auto const& e : index) {
std::cout << " " << i << " -> " << e << std::endl;
i++;
}
}
std::cout << "handlers addresses:" << std::endl;
std::cout << " do_opcode_1: " << reinterpret_cast< void* >(&do_opcode_1) << std::endl;
std::cout << " do_opcode_2: " << reinterpret_cast< void* >(&do_opcode_2) << std::endl;
std::cout << " do_opcode_bla: " << reinterpret_cast< void* >(&do_opcode_bla) << std::endl;
std::cout << " not_implemented: " << reinterpret_cast< void* >(&enums::signature_of< op_code >::not_implemented) << std::endl;
{
std::cout << "dispatcher::handlers array:" << std::endl;
size_t i = 0;
for(auto const& e : op_code_dispatcher::handlers) {
std::cout << " " << i << ": " << reinterpret_cast< void* >(*e.target< decltype(&enums::signature_of< op_code >::not_implemented) >()) << std::endl;
i++;
}
}
op_code_dispatcher dispatcher;
dispatcher[DO_OPERATION_1]("foo", "bar");
dispatcher[DO_OPERATION_2]("foo", "bar");
dispatcher[DO_OPERATION_BLA]("foo", "bar");
dispatcher[DO_OPERATION_UNBOUND]("foo", "bar");
dispatcher[op_code(123)]("foo", "bar");
return 0;
}
L'output:
Code:
indexer::values array:
0: 6
1: 7
2: 8
3: 12
indexer::index array:
6 -> 0
7 -> 1
8 -> 2
9 -> 4
10 -> 4
11 -> 4
12 -> 3
13 -> 4
handlers addresses:
do_opcode_1: 0x401150
do_opcode_2: 0x4010e0
do_opcode_bla: 0x401070
not_implemented: 0x4011c0
dispatcher::handlers array:
0: 0x401150
1: 0x401070
2: 0x4011c0
3: 0x4010e0
4: 0x4011c0
doing opcode 1
doing opcode 2
doing opcode bla
Not implemented.
Not implemented.
L'avantage c'est qu'on a juste besoin de l'enum, d'un mpl::vector contenant les valeurs de l'enum (même pas besoin de trier, c'est fait tout seul, et les valeurs en double sont aussi gérées avec le même handler), et de la signature des handlers. Cette partie là peut même se générer super facilement via une macro qui wrappe la déclaration de l'enum.
Ensuite, pour chaque fonction handler, il faut l'enregistrer comme handler pour tel ou tel valeur de l'enum. On peut même facilement enregistrer le même handler pour plusieurs valeurs de l'enum, ou pour plusieurs enums différents. Ca a l'avantage de découpler la liste des valeurs d'un éventuel switch qu'il faudrait maintenir avec à la fois la bonne liste de valeurs et les bons mappings.
Ca gère les op-code inconnus avec un callback par défaut automatiquement bindé à toutes les valeurs inconnues, et c'est certainement vachement optimisé car basé sur des tableaux compile-time (ouais en fait j'en sais rien).
Pour voir le machin en action, il suffit de mettre le second fichier en dessous de l'autre dans un .cpp et de compiler avec "gcc -std=c++11" et avec boost >= 1.49 installé quelque part.
Niveau implémentation, en fait j'ai une première structure qui va me créer un std::array avec les valeurs de l'enum (indexer::values), et un deuxième de mapping inverse (indexer::index) avec en index les valeur de l'enum et en contenu l'index où cette valeur est positionnée dans indexer::values, ou rempli avec 1 + la plus grande valeur de l'enum si l'enum ne contient rien à cet index. Pour économiser de la place, cet index est décalé par rapport à la valeur de l'enum la plus petite.
Ensuite, j'ai une autre structure dispatcher, qui va re-créer un tableau similaire à indexer::values, mais au lieu des valeurs de l'enum, celui-ci contient des std::function vers chacun des handlers enregistrés (qui sont récupérés via l'implémentation par défaut de get_handler ou celle spécialisée par l'utilisateur et associant une fonction à une valeur de l'enum). Et il utilise ensuite le tableau indexer::index pour récupérer le bon handler à partir de la valeur de l'enum passée à l'opérateur [].
---------- Post added at 16h30 ---------- Previous post was at 16h22 ----------
Edit: J'ai pas vraiment tuné le bouzin, comme je disais je n'ai pas non plus vérifié qu'il ne générait pas des trucs inutiles. Il y a peut être moyen de rajouter des constexpr partout pour éviter qu'il mette du code au run-time.