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

爵士头像

我基本上是尝试编写自己的游戏引擎来进行练习和个人使用(我知道,这几乎是不可能的任务,但是正如我所说,这主要是为了学习新事物)。

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

给出以下伪代码:

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

由于数组的大小是由template参数指定的,因此我很难为该结构声明合适的构造函数。我的最终目标将是这样的:

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

当然,这是非逻辑语法,但是我以这种方式获得的最接近的东西是C ++ 17 fold表达式

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

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

  1. 聚合初始化不起作用。

    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
    
  2. 直到编译时给定错误的参数(例如,向量大小不正确的向量,都不适合矩阵的列)时,它才会显示错误。

  3. 错误的根源并不总是很清楚。

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

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


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

爵士头像

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

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

缺点:


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

我希望这对其他人有帮助,并在初始化泛型类时简要介绍这些选项。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

C ++ 17折叠表达式语法?

Cout中的C ++ 17折叠表达式

是否可以使用C ++ 17折叠表达式折叠部分包装?

我可以从C ++ 17折叠表达式中解析出各个函数吗

C ++ 17折叠表达式计算阶乘时出错

在C ++ 14中,对此C ++ 17折叠表达式有什么好的替代方案?

c ++ 17折叠表达式打印功能,可支持2,0000个元素

模板函数中的 C++ 初始化 - 将新的 Initializer 表达式列表视为复合表达式

C ++-从'node'类型的表达式对'node *&'类型的引用的无效初始化

c ++:从类型'int *'的表达式对类型'const int *&'的引用的初始化无效

带有表达式的C ++初始化列表

C ++类模板初始化问题

c ++模板类,初始化()与{}

C ++ 17折叠语法以测试向量组成

C ++可变参数模板折叠表达式

用模式vs编译的正则表达式C#初始化正则表达式

C++ 套接字错误(新的初始化表达式列表被视为复合表达式)

使用表达式列表在 C++ 20 中初始化多维数组

错误:在C中初始化二维结构变量时的预期表达式

C ++在构造函数中使用表达式初始化引用

C ++在初始化表达式中强制左值保持不变

在C中使用错误进行结构初始化:预期表达式

使用AsParallel进行C#包装数组初始化-表达式树

仅为特定类型初始化 C++ 模板类的成员

用于double类型的静态类成员的常量表达式初始化程序

C++17 中的 Lambda 表达式:尾随返回类型与用于类型转换的 static_cast

C ++:默认在模板函数中初始化整数类型

C++模板类静态成员初始化

在类中初始化模板对象(C ++)