如何编写对引用的转发元组进行操作的constexpr函数?

时间:2019-02-10 17:56:52

标签: c++ c++17

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

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

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

我可以使用指针的元组而不是引用的元组来解决问题,但是我编写的东西的API(一组模板化的函数集,可以简化为微控制器编写SPI和I²C驱动程序的工作)将变得不那么干净。

感谢您的帮助。

Ps:我正在使用c ++ 17标准的gcc8.2。

亚历山大(Alexandre)

#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 个答案:

答案 0 :(得分:1)

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

在此函数中,tup不是常量表达式,因此不能在constexpr变量的初始化程序中使用。

  

[expr.const]¶2

     

表达式e核心常量表达式,除非对e [...]的求值将对以下表达式之一求值:

     
      
  • [...]
  •   
  • 一个 id-expression ,它引用引用类型的变量或数据成员,除非引用具有前面的初始化且      
        
    • 使用常量表达式或
    • 进行初始化   
    • 其生命周期始于对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_tupleis_tuple都存在缺陷,因为如果它们的参数是引用,则不能在常量表达式中使用它们。如果要使用类似函数的语法,则需要Boost.Hana中的type_c之类的东西:

template <typename T>
class type {};
template <typename T>
constexpr 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));

答案 1 :(得分:1)

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值时,这可能会变得很方便。请注意,std::integral_constant可隐式转换为其::type。隐式转换是Boost.Hana引入自己的“整数常数类型”的原因之一。在Boost.Hana's chapter on Compile-time numbers中可以找到一些有趣的示例。无论如何,这是带有隐式可转换std::integral_constant的简单示例:

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

答案 2 :(得分:0)

因此,这里是对至少在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();
}

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

亚历山大(Alexandre)