如何在varadic/template类构造函数中正确使用std::forward

How to properly use std::forward in variadic/template class constructor

本文关键字:std forward 构造函数 varadic template      更新时间:2023-10-16

我的模板"matrix"类的std::forward构造函数有问题。基本上,我想设置一个类型为float、大小为4的矩阵,等于2个类型为浮点、大小为3的矩阵的和。我在函数"test"中的结构"matrix_struct"中执行此操作。然而,MSVC错误告诉我"‘static_cast’:无法从‘matrix’转换为‘float’",每当我检查错误时,它都会将我带到带有std::forward的第三个矩阵构造函数。

///////////////////////////////////
somefile.hpp
#pragma once
#include "matrix.hpp"
using matrix3 = matrix<float, 3>;
using matrix4 = matrix<float, 4>;
struct matrix_struct {
matrix4 sum;
void test(const matrix3& a, const matrix3& b) 
{
sum = a + b;
}
}
///////////////////////////////////
matrix.hpp
#pragma once
#include <array>
template <typename t, size_t dim>
class matrix
{
public:
matrix() { data.fill(static_cast<t>(0) }
explicit matrix(const std::array<t, dim>& a) : data(a) {}
template <typename... args_t>
matrix(args_t... args) : data{ static_cast<t>(std::forward<args_t>(args))... } }
public:
t& at(const size_t index)
{
return data.at(index >= dim ? dim - 1 : index);
}
const t& at(const size_t index) const
{
return data.at(index >= dim ? dim - 1 : index);
}
public:
matrix& operator = (const matrix<t, dim>& other)
{
for (size_t i = 0; i < dim; ++i) {
at(i) = other.at(i);
}
return *this;
}
matrix& operator = (const std::array<t, dim>& other)
{
for (size_t i = 0; i < dim; ++i) {
at(i) = other.at(i);
}
return *this;
}
matrix& operator = (const t& other) 
{
for (size_t i = 0; i < dim; ++i) {
at(i) = other;
}
return *this;
}
public:
matrix operator + (const matrix<t, dim>& other) const
{
matrix<t, dim> ret;
for (size_t i = 0; i < dim; ++i) {
ret.at(i) = at(i) + other.at(i);
}
return ret;
}
matrix operator + (const std::array<t, dim>& other) const
{
matrix<t, dim> ret;
for (size_t i = 0; i < dim; ++i) {
ret.at(i) = at(i) + other.at(i);
}
return ret;
}
matrix operator + (const t& other) const
{
matrix<t, dim> ret;
for (size_t i = 0; i < dim; ++i) {
ret.at(i) = at(i) + other;
}
return ret;
}
private:
std::array<t, dim> data;
};

模板构造函数存在问题。他们通常会创建比其他构造函数更好的候选代码。

一般的解决方案是,如果模板的腐朽类型与您正在编写的类匹配,则禁用该模板。

示例:

struct MyClass
{
template
<
class Arg,
class...Rest,
std::enable_if_t
<
! std::is_same
<
std::decay_t<Arg>,
MyClass
>::value
>* = nullptr
>
MyClass(Arg&& arg, Rest&&...rest)
{
// code to construct from something that's not MyClass
// this will no longer hijack copy constructors etc.
}

};

@RichardHodges的回答解决了代码示例的第一个问题。假设您包含他的解决方案来克服棘手的复制/移动构造函数选择,那么另一个问题仍然存在:您不能通过构造函数/赋值运算符提供矩阵升级/降级服务。

因此,您的测试函数中的以下行:

sum = a + b; // a + b is a matrix<float, 3>, sum a matrix<float, 4>

将触发对可变模板构造函数的调用并失败。

从Richard的解决方案开始,您需要稍微调整SFINAE条件,将其扩展到任何大小的矩阵。要做到这一点,我们需要一点is_matrix特征:

template <typename T, size_t Dim>
class matrix;    
template <typename T>
struct is_matrix : std::false_type {};
template <typename Num, size_t Size>
struct is_matrix<matrix<Num, Size> > : std::true_type {
using value_type = Num;
};

现在可变模板构造函数变成:

template <typename t, size_t dim>
class matrix
{
/* ... */
public:
/* ... */
template
<
class Arg,
class...Rest,
std::enable_if_t
<
! std::is_matrix
<
std::decay_t<Arg>
>::value
>* = nullptr
>
matrix(Arg&& arg, Rest&&...rest)
{
// code to construct from something that's not a matrix
// this will no longer hijack copy constructors etc.
}
};

然后,我们需要添加适当的matrix构造函数以及适当的友元声明:

template <typename t, typename dim>
class matrix {
public:
template <typename OtherT, size_t OtherDim>
friend class matrix;
template <size_t OtherDim>
matrix(matrix<t, OtherDim> const& other) {
size_t i = 0;
for (; i < min(OtherDim, dim); ++i) {
data[i] = other.data[i];
}
for(; i < dim; ++i) {
data[i] = t();
}
}
template <typename OtherT,
size_t OtherDim>
matrix(matrix<OtherT, OtherDim> const&) {
static_assert(std::is_same<t, OtherT>::value,
"value_type mismatch between matrices!");
}        
/* ... */
};

注意:您需要友元声明,因为无论何时Type1 != Type2Dim1 != Dim2matrix<Type1, Dim1>matrix<Type2, Dim2>都是完全不同的类型,因此,如果没有友元声明就无法访问matrix<t, dim>matrix<OtherT, OtherDim>的私有/受保护成员

当值类型匹配时,此实现将通过用给定矩阵的内容填充其数据成员来初始化目标矩阵:

  • 如果给定的矩阵较大,它将被截断
  • 如果给定的矩阵较小,则剩余元素将被0初始化

如果值类型不匹配,那么不太专业的matrix<OtherT, OtherDim>构造函数是唯一可用的重载,它会通过static_assert触发编译器错误。

您还需要定义等效的赋值运算符。。。我把它留作练习。

这些构造函数在操作中的演示可以在Coliru上找到