【问题标题】:Create repeated declaration创建重复声明
【发布时间】:2020-07-30 07:39:58
【问题描述】:

我想知道是否有人知道创建重复类型声明的方法,因为这可能会造成混淆,所以一个示例会有所帮助:

对于我们的项目,我们需要有如下函数和类型声明:

    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>;

分散在多个文件中,多次,最多 32 个双精度数(仅双精度数,如果这很重要)。

我很乐意用以下虚构代码行中的内容替换这些双打的手动写入/计数:

    using FunType = something_generate_function_decl<8>;
    using DataType = something_generate_datatype_decl<8>

如果可能的话,我想远离 boost 及其预处理器库。

编辑以提供一些说明

总体情况是,在应用程序的某个时刻,我们得到一系列字节(表示一个双精度数组),每个值都具有预定义的含义,我们需要对它们进行验证(每个值都需要验证有几个有效性条件),拒绝无效数据,记录相应的无效数据及其含义,将有意义的数据传递给应用程序,从记录器模块到十几个不同的地方,包括现有的功能和已经定义的Qt信号语法等......在某些时候这会从我们手中消失,所以这就是为什么我想创建尽可能容易阅读和可验证的代码。

Edit2 提供更多说明

阅读 cmets,似乎对这个问题的范围有很多困惑。问题中故意遗漏了很多信息,这些信息与问题的纯粹本质无关,即如何将这 8 个(或 9 个、12 个或 32 个双打)缩短为更易于管理的实体。丢失的信息是我们处理接收到的数据的内部方式(我们有一个大规模的项目,因此您可以想象数据有几层抽象,可以进行自动验证、转换等......在发送之前,所以不是整个应用程序都是一长串 if/else 语句和带参数的基本函数调用),整个事情的唯一限制是作为一个简单的双精度数组(代表一条消息)出现的是验证然后发送到已经具有预定义接口的函数/QT 信号/C++11 lambda。所有信息处理和验证都封装在类层中,这些类只是在某处注册自己(超出此问题的范围)以接收、存储、验证和发送数据,例如,FunType(实际上是内部使用的消息类的类型)表示将发送特定消息(及其验证成员)的函数/插槽/lambdas的接口,通过某种机制自动将所有成员收集到一个元组中(DataType)并使用一些带有可变参数模板的index_sequence 魔术,编译器将元组匹配到所需的函数,该函数在某个时间点“订阅”了此消息。 @admin 如果您觉得此编辑不相关,请随时将其删除。

【问题讨论】:

  • 使用双精度的 array 不是更好吗?
  • @DanielLangr 我们也尝试过,但是代码的可维护性在某些时候降到了零,所以我们决定制作一些人类可读的函数
  • std::array&lt;double, 8&gt;lessdouble, double, double, double, double, double, double, double 可维护性如何?
  • @没用的考虑这个(不是真正的代码,只是示例):void fun(std::array&lt;double, 3&gt; a) { if(a[0] &gt; a[1] &amp;&amp; a[1] &lt; 90 &amp;&amp; a[1] &gt; 0 &amp;&amp; a[3] &gt; 0) fly(a[0], a[1], a[2]) } vs. void fun(double latitude, double longitude, double altitude) { if(latitude &gt; longitude &amp;&amp; longitude &lt; 90 &amp;&amp; longitude &gt; 9 and altitude &gt; 0) fly(longitude, latitude, altitude); } 你更了解它的作用是哪一个?
  • 因此,您的问题中缺少的信息是 8 个(或 32 个)双精度不是平面的非结构化列表,而是实际上命名的变量。在这种情况下,您应该使用具有命名成员的结构,或者使用具有元组和命名 accessors 的结构。

标签: c++ templates


【解决方案1】:

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>;

没有 Boost 的替代 C++14 解决方案:

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;

此解决方案需要默认可构造的Tdouble 满足此要求。

要解除这个要求,我们可以使用type_identity wrapper(将成为 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>{}));
};

【讨论】:

  • 很好,我也尝试过使用返回类型,但我没有设法让它工作。我认为您可以使用std::declval 来允许任何Ts:static Fn&lt;std::remove_reference_t&lt;decltype(is, std::declval&lt;T&gt;())&gt;...&gt; 。但它不适用于引用,这可以通过将 remove_ref_t 替换为 T 本身的 copy_cv_ref_t 来实现。
  • @Quimby,感谢您的建议。我故意使用T{} 来简化代码(OP 声明只需要支持double)。我可以建议使用 type_identity 包装器,而不是 std::declval。将其添加到答案中。
  • 那更好,我不知道,如果还不支持C++20,它甚至有一个简单的实现。
【解决方案2】:

这里不用把Boost拖进去,这里是C++11的解决方案:

#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,"");
}

【讨论】:

  • 最棒的!尽管我个人认为这样的类型是一个糟糕的设计决策。
  • 谢谢,我同意,但 OP 似乎经验丰富,问题也很清楚。此外,这也是一个有趣的元编程练习。
【解决方案3】:

这是没有 Boost 或宏的纯 C++ 解决方案,只是可变参数模板:

#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)>>{} );
 }

【讨论】:

    【解决方案4】:

    如果您可以使用 Boost,那么它的 预处理器 库中有一个宏 BOOST_PP_ENUM 可以提供帮助。这应该有效:

    #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) >;
    

    现场演示:https://godbolt.org/z/537n74

    或者,使用额外的辅助宏更舒适:

    #define NDOUBLE(n) BOOST_PP_ENUM(n, TEXT, double)
    
    using FunType = std::function<void( NDOUBLE(8) )>;
    using DataType = std::tuple< NDOUBLE(8) >;
    

    现场演示:https://godbolt.org/z/vq1jrY

    【讨论】:

    • 好的sln,使用宏!
    【解决方案5】:

    tl;dr - 这是一个框架挑战,因为我认为你问错了问题。

    对于我们的项目,我们需要有如下函数和类型声明:

     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>;
    

    好吧,不要。如果你这么喜欢无意义的重复,你应该用手而不是用电脑来计算。

    我很乐意用以下虚构代码行中的内容替换这些双打的手动写入/计数:

    using FunType = something_generate_function_decl<8>;
    

    你的愿望太低了。你只是在自动生成糟糕的代码,而你本可以编写好的代码。

    你最后一个不情愿地透露了你想要处理的实际例子:

    void fun(double latitude, double longitude, double altitude)
    {
      if(latitude > longitude &&
         longitude < 90 && longitude > 9 &&
         altitude > 0)
      {
        fly(longitude, latitude, altitude);
      }
    }
    

    (一旦我删除了标签并混合使用 &amp;&amp;and没有让我对现有代码的质量放心)......会更好处理通过使用具有命名字段的结构化类型而不是您要求的任何东西。

    struct LatLongPosition // just in case you have alternative representations
    {
        double latitude;
        double longitude;
    };
    
    struct AirPosition
    {
        LatLongPosition pos;
        double altitude;
    };
    

    现在,您的函数参数可以是强类型的:您可以保证在编译时传递正确类型的位置(如果您有多个),而不是接受任何 8 或 32 个双精度数的序列,和位置而不是速度或动量或其他任何东西。

    请注意,还有很多需要进一步改进的空间,但基础是首先使用结构化数据类型,而不是大量的平面参数列表。

    【讨论】:

    • 虽然我可以在您的论点中看到有效点,但我们的应用程序只是整个基础架构的消息传递部分,因此我们不需要创建结构,因为我们不处理数据。我们只需要确保作为原始双精度数组到达的内容是有效的,并将其发送到已经编写好的函数(其中一些是 C 函数,其中一些是 Qt 信号,同样具有非常明确的定义)界面)。问题中的这个简短代码序列只是应用程序“自动”解压缩、验证、转发机制的一部分。
    • 验证正在处理数据。你需要知道数据代表什么来验证它,而扁平的参数列表是一种糟糕的方式来编码这些信息。
    猜你喜欢
    • 2012-10-24
    • 2020-04-24
    • 1970-01-01
    • 1970-01-01
    • 2021-09-21
    • 1970-01-01
    • 1970-01-01
    • 2016-10-22
    • 1970-01-01
    相关资源
    最近更新 更多