C++17 模板类初始化的折叠表达式的限制类型

Posted

技术标签:

【中文标题】C++17 模板类初始化的折叠表达式的限制类型【英文标题】:C++17 Limit type of fold expression for initialization of a template-class 【发布时间】:2018-06-25 11:40:23 【问题描述】:

我基本上尝试编写自己的游戏引擎以供练习和个人使用(我知道,这几乎是一项不可能完成的任务,但正如我所说,它主要用于学习新事物)。

目前,我正在研究我的数学库(主要是向量和矩阵),我遇到了一个有趣但主要是美学问题。

给出如下伪代码:

template <uint8 size>
struct TVector 
    float elements[size];
;

现在我希望能够以所需数量的浮点数作为参数来构造结构:

TVector<3> vec0(1.0f, 2.5f, -4.0f); 
TVector<2> vec1(3.0f, -2.0f);

TVector<3> vec2(2.0f, 2.2f); // Error: arg missing 
TVector<2> vec3(1.0f, 2.0f, 3.0f) // Error: too many args

由于数组的大小是由模板参数给出的,所以我很难为结构声明一个合适的构造函数。我的最终目标是这样的:

// This is pseudo-ideal-code
TVector(size * (float value)); // Create a constructor with number of size 
                               // parameters, which are all floats

当然,这是非逻辑语法,但我以这种方式实现的最接近的事情是使用 C++17 折叠表达式

template<typename... Args>
    TVector(Args... values) 
        static_assert(sizeof...(values) <= size, "Too many args");
        uint8 i = 0;
        (... , void(elements[i++] = values));
    

它在填充数组的意义上工作得非常好,而且(我猜)开销不大,但对于使用这个结构的程序员来说,它也容易出错,因为它没有直接指示 有多少 构造函数接受的参数。

此外,它没有指定参数应该是哪种类型,这是我最大的问题。

如果有效,为什么会有问题?

假设有以下结构,它使用 TVector 结构:

template <const uint8 rows, const uint8 columns>
struct TMatrix 
    // elements[-columns-][-rows-]; 
    TVector<rows> elements[columns];

鉴于构造函数类似于向量结构的折叠表达式, 我希望能够使用相应大小的向量或大括号初始化来构造矩阵。

    聚合初始化不起作用。

    TVector<2> vec(1.0f, 3.0f);
    TMatrix<2, 2> mat0(vec, vec); // Works
    TMatrix<2, 2> mat1(vec, 0.2f, -4.2f); // Error
    // Does not compile, because the Type is not clear
    

    当给定错误的参数时,直到编译才会显示错误 (就像一个大小错误的向量,不适合作为矩阵的列)。

    错误的来源并不总是很清楚。

TL;DR:现在终于要问我真正的问题了:

有没有办法限制折叠表达式的类型,最终根本不使用模板并解决我上面给出的 3 个问题?

我想像这样:

TVector(float... values)  
// Maybe even specify the size of the pack with the size given in the struct template
    uint8 i = 0;
    (... , void(elements[i++] = values));

还有:

TMatrix(const TVector<rows>&... values) 
    uint8 i = 0;
    (..., void(elements[i++] = values));

当然,我在这里很挑剔,这主要是审美问题,但我认为这是一个重要的设计决策,可以真正提高代码的可用性。


感谢您阅读本文并帮助我解决我的第一个问题 :)

【问题讨论】:

如果所有类型都不能转换为双精度,您可以使用启用创建扣除指南。 【参考方案1】:

所以在深入研究模板元编程并尝试之后,我遇到了一些解决方案(都有自己的小问题)。

std::initializer_list:

优点:

易于实施:

// Constructors:
TVector(std::initalizer_list<float> values);
TMatrix(std::initalizer_list<TVector<rows>> values);

大括号初始化:

TVector<3>    vec  1.0f, 0.0f, 2.0f ;
TMatrix<3, 3> mat  vec,  3.0f, 4.0f, 1.0f , vec ;

缺点:

复制开销 无法移动值 未指定允许的参数数量 我建议 Andrzej 关于该主题的 C++ 博客:The cost of std::initializer_list

std::array

​​>

优点:

易于实施:

// Constructors:
TVector(std::array<float, size>&& values);
TMatrix(std::aray<TVector<rows>, columns>&& values);

如果数组中的对象可移动,则可移动

缺点:

大括号初始化极其丑陋

TVector<3>    vec   1.0f, 0.0f, 2.0f  ;
TMatrix<3, 3> mat  vec, TVector<3>  3.0f, 4.0f, 1.0f  , vec ;

折叠表达式 - 我的首选解决方案

优点:

无开销 可移动

使用统一初始化

TVector<3>    vec  1.0f, 0.0f, 2.0f ;
TMatrix<3, 3> mat  vec, TVector<3> 3.0f, 4.0f, 1.0f , vec ;
可以根据构造函数的需要指定

缺点:

难以实施和指定

在没有指定类型的情况下不允许嵌套大括号(据我所知)

// Constructors:
template<typename... Args, std::enable_if_t<
    is_pack_convertible<float, Args...>::value && 
    is_pack_size_of<columns, Args...>::value, bool> = false >
TVector(std::array<float, size>&& values);

template<typename... Args, std::enable_if_t<
    is_pack_convertible<Vector<rows>, Args...>::value && 
    is_pack_size_of<columns, Args...>::value, bool> = false >
TMatrix(std::aray<TVector<rows>, columns>&& values);

is_pack_convertible / is_pack_size_of

// Declaration - checks if all types of a pack are convertible to one type
template <typename To, typename... Pack> struct is_pack_convertible;
// End of pack case
template <typename To> struct is_pack_convertible<To> : std::true_type ;
// Recursive bool &&
template <typename To, typename From, typename... Pack>
struct is_pack_convertible<To, From, Pack...> 
    static constexpr bool value = std::is_convertible<From, To>::value
        && is_pack_convertible<To, Pack...>::value;
;

// Declaration - checks if the pack is equal to a certain size
template <size_t size, typename... Pack> struct is_pack_size_of;
// End of pack: size is equal
template <> struct is_pack_size_of<0> : std::true_type ;
// End of pack: size is not equal
template <size_t remainder> struct is_pack_size_of<remainder> : std::false_type ;
// Count down size for every element in pack
template <size_t size, typename Arg, typename... Pack> 
struct is_pack_size_of<size, Arg, Pack...> 
    static constexpr bool value = is_pack_size_of<size - 1, Pack...>::value;
;

我希望这对其他人有所帮助,并简要概述了初始化泛型类时的选项。

【讨论】:

【参考方案2】:

使用间接,您可以执行以下操作:

template <typename Seq> struct TVectorImpl;

template <std::size_t, typename T> using force_type = T;

template <std::size_t ... Is>
struct TVectorImpl<std::index_sequence<Is...>>

    TVectorImpl(force_type<Is, float>... args) : elementsargs... 

    float elements[sizeof...(Is)];
;

template <std::size_t size>
using TVector = TVectorImpl<decltype(std::make_index_sequence<size>())>;

这避免了也有模板方法(所以构造为2.4, 5 工作)。

Demo

请注意,它是在 C++14 中完成的(index_sequence 可以在 C++11 中完成)。

【讨论】:

以上是关于C++17 模板类初始化的折叠表达式的限制类型的主要内容,如果未能解决你的问题,请参考以下文章

掌握C++17核心特性

掌握C++17核心特性

C++17新特性汇总

C++17 新特性精华大全

第4章 可变参数模板:4.2 折叠表达式

他来了,他来了,C++17新特性精华都在这了