分享
三行代码  ›  专栏  ›  技术社区  ›  user3897188

如何编写constexpr函数来操作转发的引用元组? - How to write constexpr function which operate on forwarded tuple of reference?

  •  5
  • user3897188  · 技术社区  · 8 月前

    我写了一个constexpr函数,它计算一个元组元素的大小之和。

    当直接调用时,函数调用使用值的元组和引用的元组进行编译。

    当通过模板化函数调用时,它仍然使用值的元组进行编译,但使用引用的元组失败。

    我可以使用指针的元组而不是引用的元组来解决我的问题,但是我编写的东西的API(一组模板化的函数,以便于为微控制器编写SPI和IC驱动程序)将不太干净。

    谢谢你的帮助。

    PS:我使用GCC82使用C++ 17标准。

    亚历山大

    #include <tuple>
    
    template <typename> struct is_tuple_t: std::false_type {};
    template <typename ...T> struct is_tuple_t<std::tuple<T...>> : std::true_type {};
    
    template<typename Type>
    constexpr bool is_tuple(const Type&) {
        if constexpr (is_tuple_t<Type>::value) 
            return true;
        else
            return false;
    }
    
    template<class F, class...Ts, std::size_t...Is>
    constexpr void for_each_in_tuple(const std::tuple<Ts...> & tupl, F func,
                 std::index_sequence<Is...>){
        using expander = int[];
        (void)expander { 0, ((void)func(std::get<Is>(tupl)), 0)... };
    }
    
    template<class F, class...Ts>
    constexpr void for_each_in_tuple(const std::tuple<Ts...> & tupl, F func){
        for_each_in_tuple(tupl, func, std::make_index_sequence<sizeof...(Ts)>());
    }
    
    template <typename T>
    constexpr size_t size_of_tuple(const T &tup) {
        static_assert(is_tuple(tup) == true, "error size_of_tuple argument must be a tuple");
        size_t s=0;
        for_each_in_tuple(tup, [&s](auto &&x) {
            s += sizeof(x);
        });
        return s;
    }
    
    template<typename Tb>
    constexpr size_t foo(const Tb&& tup)
    {
        constexpr size_t st = size_of_tuple(tup);
        return st;
    }
    
    int main()
    {
        uint16_t var;
    
        constexpr size_t s1 = size_of_tuple(std::make_tuple(1)) ;          // OK
        constexpr size_t s2 = size_of_tuple(std::forward_as_tuple(var)) ;  // OK
    
        constexpr size_t f1 = foo(std::make_tuple(1)) ;          // OK
        constexpr size_t f2 = foo(std::forward_as_tuple(var)) ;  // FAIL
    }
    
    3 回复  |  直到 8 月前
        1
  •  1
  •   Oktalist    8 月前
    template<typename Tb>
    constexpr size_t foo(const Tb&& tup)
    {
      constexpr size_t st = size_of_tuple(tup);
      return st;
    }
    

    在这个函数中, tup 不是常量表达式,因此不能在初始值设定项中使用 constexpr 变量。

    [ Exp.const ] 2

    表达式 e 是一个 核心常量表达式 除非评估 e […]将计算以下表达式之一:

    • […]
    • ID表达式 引用引用类型为引用类型的变量或数据成员,除非引用具有前面的初始化,并且
      • 它是用常量表达式初始化的,或者
      • 它的生命开始于 e

    这两种方法中的任何一种都应该有效:

    template<typename Tb>
    constexpr size_t foo(const Tb&& tup)
    {
      size_t st = size_of_tuple(tup);
      return st;
    }
    
    template<typename Tb>
    constexpr size_t foo(const Tb&& tup)
    {
      return size_of_tuple(tup);
    }
    

    请注意,对于同一规则的其他违反,clang仍然拒绝您的代码。


    最终,你的 size_of_tuple is_tuple 两者都有缺陷,因为如果它们的参数是引用,则不能在常量表达式中使用。如果要使用类似语法的函数,则需要类似 type_c 来自boost.hana:

    template <typename T>
    class type {};
    template <typename T>
    type<T> type_c{};
    
    template <typename T>
    constexpr bool is_tuple(type<T>) {
        return is_tuple_t<T>::value;
    }
    
    template <typename T>
    constexpr size_t size_of_tuple(type<T> tup) {
        static_assert(is_tuple(tup), "size_of_tuple argument must be a tuple");
        //...
    }
    
    template<typename Tb>
    constexpr size_t foo(const Tb&& tup)
    {
      size_t st = size_of_tuple(type_c<std::remove_cvref_t<decltype(tup)>>);
      return st;
    }
    
    uint16_t var = 42;
    constexpr size_t f2 = foo(std::forward_as_tuple(var));
    
        2
  •  1
  •   Julius    8 月前

    您最初的方法的问题在 Oktalist's answer .

    注意 size_of_tuple 可以在一行中使用 C++17's fold expressions :

    template<class... Ts>
    constexpr std::size_t size_of_tuple(const std::tuple<Ts...>&) {
      return (0 + ... + sizeof(Ts));
    }
    

    但是,由于类型为 const std::tuple<Ts...>& . 因此,可能需要引入一个可以“传递”给这些元函数的空标记类型。这个想法在 Boost.Hana 's chapter on type computations .

    下面是一个完整的例子。

    static_assert(__cplusplus >= 201703L, "C++17 required");
    
    #include <cstddef>
    #include <cstdint>
    
    #include <tuple>
    
    // the empty tag type
    template<class T>
    struct Type {};
    
    ////////////////////////////////////////////////////////////////////////////////
    
    template<class... Ts>
    constexpr std::size_t size_of_tuple(Type< std::tuple<Ts...> >) {
      return (0 + ... + sizeof(Ts));
    }
    
    static_assert(0 == size_of_tuple(Type< std::tuple<> >{}));
    static_assert(12 == size_of_tuple(Type< std::tuple<int32_t, int64_t> >{}));
    static_assert(12 == size_of_tuple(Type< std::tuple<int32_t&, int64_t&> >{}));
    static_assert(2 == size_of_tuple(Type< std::tuple<char&, char> >{}));
    static_assert(6 == size_of_tuple(Type< std::tuple<int32_t&, char&, char> >{}));
    
    // fails to compile (for good reasons):
    //static_assert(8 == size_of_tuple(Type< std::tuple<int32_t, uint64_t> >{}));
    

    此外,您可以考虑使用 std::integral_constant 作为返回类型。当您想将大小传递给另一个函数并将其用作 constexpr 价值。注意 标准::积分常数 s隐式地可转换为 ::type . 隐式转换是其中一个原因 哈娜 介绍了它自己的“积分常数类型”。一些有趣的例子可以在 Boost.Hana 's chapter on Compile-time numbers . 不管怎样,这里有一个简单的例子 标准::积分常数 :

    #include <cstddef>
    
    #include <tuple>
    #include <type_traits>
    
    template<class... Ts>
    constexpr auto better_size_of_tuple(Type< std::tuple<Ts...> >) {
      constexpr std::size_t ret = (0 + ... + sizeof(Ts));
      return std::integral_constant<std::size_t, ret>{};
    }
    
        3
  •  0
  •   user3897188    8 月前

    下面是对编译函数的重写,至少使用GCC 8.2:

    template
    <std::size_t position, class T>
    struct SizeOfTupImpl{
    static constexpr size_t
    size() {
      if constexpr (position != 0) {
      return sizeof(std::tuple_element_t<position, T>) +
             SizeOfTupImpl<position - 1, T>::size();
      } else {
      return sizeof(std::tuple_element_t<0, T>);
      }
    }
    };
    
    template<class T>
    constexpr size_t
    size_of_tuple(const T& tup) {
      static_assert(is_tuple(tup) == true, "error size_of_tuple argument must be a tuple");
      constexpr std::size_t dimension = std::tuple_size<T>::value;
      return SizeOfTupImpl<dimension - 1, T>::size();
    }
    

    由于我是一个绝对模板元编程初学者,请随时为我指出一个更优雅的解决方案!

    亚历山大