Stack Overflow Asked by Ferenc Deak on November 3, 2020
I was wondering if anyone knows a way of creating repetitive type declarations, and since this might be confusing an example will be helpful:
For our project we need to have function and type declarations like:
using FunType = std::function<void(double,double,double,double,double,double,double,double)>;
using DataType = std::tuple<double,double,double,double,double,double,double,double>;
scattered around several files, multiple times, up to 32 doubles (only doubles, if this is important).
I would be happy to replace the manual writing/counting of those doubles, with something in the lines of the following imaginary code:
using FunType = something_generate_function_decl<8>;
using DataType = something_generate_datatype_decl<8>
If possible I would like to stay away from boost and its preprocessor library.
Edit to provide some clarification
The big picture is that at some point in the application we get in a series of bytes (representing an array of doubles) with each value having a predefined meaning, we need to do validation on them (each value needs to be verified with several conditions for validity), reject data that is not valid, log the corresponding invalid data with its meaning, pass the meaningful data around the application, from logger module to a dozen of different places including existing functions and also Qt signals with already defined syntax etc… and at some point this will go out from our hands, so that’s why I would like to create code as easily readable and verifiable as possible.
Edit2 to provide more clarification
Reading the comments, seemingly there is a lot of confusion about the scope of this question. There is intentionally a lot of information missing from the question which is not relevant to the pure essence of the question which is how to shorten those 8 (or 9, or 12, or 32 doubles) into a more manageable entity. The missing information is our internal way of dealing with the data we receive (we have a project of a large scale, so you can imagine that there are a few layers of abstraction for the data which does automatic validation, conversion, etc… before sending over, so not the entire application is a long list of if/else statements and basic function calls with parameters), the only restriction for the entire thing is that what comes in as a simple array of doubles (representing a message) is validated and then sent over to functions/QT signals/C++11 lambdas which already have a predefined interface. All the information handling and validation is encapsulated into layers of classes which just register themselves somewhere (out of scope for this question) for receiving, storing, validating and sending data, and for example, the FunType
(which actually is an internally used type of the message classes) represents the interface of the functions/slots/lambdas where the specific message (with its validated members) will be sent, automatically by some mechanism which gathers all the members into a tuple (DataType
) and the using some index_sequence
magic with variadic templates the compiler matches the tuple to the required function which at some point in time "subscribed" to this message.
@admin if you feel this edit not being relevant, feel free to delete it.
This is easy with Boost.Mp11:
#include <boost/mp11/algorithm.hpp>
#include <boost/mp11/list.hpp>
namespace mp11 = boost::mp11;
template<class... Args>
using make_fn = std::function<void(Args...)>;
using TypeList = mp11::mp_repeat_c<mp11::mp_list<double>, 8>;
using FunType = mp11::mp_apply<make_fn, TypeList>;
using DataType = mp11::mp_apply<std::tuple, TypeList>;
An alternative C++14 solution without Boost:
template<template<class...> class Fn, class T, std::size_t n>
struct apply_repeat {
template<std::size_t... is>
static Fn<decltype(is, T{})...>
get_type(std::index_sequence<is...>);
using type = decltype(get_type(std::make_index_sequence<n>{}));
};
template<class... Args>
using make_fn = std::function<void(Args...)>;
using FunType = typename apply_repeat<make_fn, double, 8>::type;
using DataType = typename apply_repeat<std::tuple, double, 8>::type;
This solution requires a default constructible T
. This requirement is satisfied for double
.
To lift this requirement we can use type_identity
wrapper (will be part of C++20):
template<class T>
struct type_identity {
using type = T;
};
template<template<class...> class Fn, class T, std::size_t n>
struct apply_repeat {
template<std::size_t... is>
static Fn<typename decltype(is, type_identity<T>{})::type...>
get_type(std::index_sequence<is...>);
using type = decltype(get_type(std::make_index_sequence<n>{}));
};
Correct answer by Evg on November 3, 2020
Here is pure C++ solution without Boost or Macros, just variadic templates:
#include <tuple>
#include <utility>
#include <functional>
// tuple
template <typename T, std::size_t ... Is>
constexpr auto gft_helper (std::index_sequence<Is...> const &)
-> decltype(std::make_tuple( (Is, std::declval<T>())... ));
template <typename T, std::size_t N>
constexpr auto get_fixed_tuple ()
-> decltype(gft_helper<T>(std::make_index_sequence<N>{}));
template <typename T, std::size_t N>
using tuple_fixed_type = decltype(get_fixed_tuple<T, N>());
// function
template <typename T>
constexpr auto getType(int, T&& t)
-> typename std::decay<T>::type;
template <typename T, std::size_t ... Is>
constexpr auto gff_helper (std::index_sequence<Is...> const &)
-> std::function<void( decltype(getType(Is, std::declval<T>()))...)>;
template <typename T, std::size_t N>
constexpr auto get_fixed_function ()
-> decltype(gff_helper<T>(std::make_index_sequence<N>{}));
template <typename T, std::size_t N>
using function_fixed_type = decltype(get_fixed_function<T, N>());
int main()
{
using FunType = function_fixed_type<double,4>;
using DataType = tuple_fixed_type<double,4>;
static_assert(std::is_same<DataType, std::tuple<double, double, double, double>>{} );
static_assert(std::is_same<FunType, std::function<void(double, double, double, double)>>{} );
}
Answered by Vasilij on November 3, 2020
No need to drag Boost into this, here is C++11 solution:
#include <cstdint>
template<std::size_t N,typename R,typename T, typename...Args>
struct function_replicator{
//Add an extra T that will be packed with Args... in the nested template.
using type = typename function_replicator<N-1,R,T,T,Args...>::type;
};
template<typename R,typename T, typename...Args>
struct function_replicator<0,R,T,Args...>{
//Args... holds N Ts
using type = R(Args...);
};
template<std::size_t N,template<typename...CArgs>class Container,typename T, typename...Args>
struct container_replicator{
using type = typename container_replicator<N-1,Container,T,T,Args...>::type;
};
template<template<typename...CArgs>class Container,typename T, typename...Args>
struct container_replicator<0,Container,T,Args...>{
using type = Container<Args...>;
};
#include <tuple>
#include <functional>
// Feel free to make them more general.
template<std::size_t N>
using function_def = std::function<typename function_replicator<N,void,double>::type>;
template<std::size_t N>
using tuple_def = typename container_replicator<N,std::tuple,double>::type;
#include <type_traits>
int main(){
//Keeping it C++11
static_assert(std::is_same<function_def<3>,std::function<void(double,double,double)>>::value,"");
static_assert(std::is_same<tuple_def<3>,std::tuple<double,double, double>>::value,"");
}
Answered by Quimby on November 3, 2020
For our project we need to have function and type declarations like:
using FunType = std::function<void(double,double,double,double,double,double,double,double)>;
using DataType = std::tuple<double,double,double,double,double,double,double,double>;
Well, don't. If you love pointless repetition so much, you should be doing your calculations by hand instead of using a computer.
I would be happy to replace the manual writing/counting of those doubles, with something in the lines of the following imaginary code:
using FunType = something_generate_function_decl<8>;
Your aspirations are too low. You're just automating the production of bad code, when you could be writing good code instead.
Your final, grudgingly-revealed actual example of what you want to handle:
void fun(double latitude, double longitude, double altitude)
{
if(latitude > longitude &&
longitude < 90 && longitude > 9 &&
altitude > 0)
{
fly(longitude, latitude, altitude);
}
}
(once I removed the tabs and the mixed use of &&
and and
which did nothing to reassure me about the quality of your existing code) ... would be much better handled by using structured types with named fields than any of the things you asked for.
struct LatLongPosition // just in case you have alternative representations
{
double latitude;
double longitude;
};
struct AirPosition
{
LatLongPosition pos;
double altitude;
};
Now, your function arguments can be strongly-typed: rather than accepting any sequence of 8 or 32 doubles, you can guarantee at compile-time you're passing the correct type of position (if you have more than one), and a position rather than a velocity or momentum or anything else.
Note that there's lots of scope to improve further, but the foundation is to be using structured data types rather than massive flat argument lists in the first place.
Answered by Useless on November 3, 2020
If you can use Boost, there's a macro BOOST_PP_ENUM in it's Preprocessor library that can help. This should work:
#include <boost/preprocessor/repetition/enum.hpp>
#define TEXT(z, n, data) data
using FunType = std::function<void( BOOST_PP_ENUM(8, TEXT, double) )>;
using DataType = std::tuple< BOOST_PP_ENUM(8, TEXT, double) >;
Live demo: https://godbolt.org/z/537n74
Or, more comfortably with additional helper macro:
#define NDOUBLE(n) BOOST_PP_ENUM(n, TEXT, double)
using FunType = std::function<void( NDOUBLE(8) )>;
using DataType = std::tuple< NDOUBLE(8) >;
Live demo: https://godbolt.org/z/vq1jrY
Answered by Daniel Langr on November 3, 2020
Get help from others!
Recent Answers
Recent Questions
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP