我写了一个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
}
答案 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_tuple
和is_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)