创建对象的可变参数模板方法
variadic template method to create object
我在模板 class(T_
类型)中有一个可变参数模板方法,看起来像这样
template < typename T_ >
class MyContainer {
public:
...
template <typename ...A>
ulong add (A &&...args)
{
T_ t{args...};
// other stuff ...
vec.push_back(t);
// returning an ulong
}
}
所以基本上我试图让这个 class 适应任何类型 T_
但是因为我无法提前知道它的构造函数需要哪些类型,所以我使用了可变参数。我从 stl 库的 emplace_back
方法中获得灵感。
尽管如此,如果我尝试执行类似的操作,我会收到一条关于缩小整数类型转换的警告
MyContainer<SomeClassRequiringAnUlong> mc;
mc.add(2);
warning: narrowing conversion of ‘args#0’ from ‘int’ to ‘long unsigned int’ [-Wnarrowing]
所以我想知道我是否可以做些什么。有没有办法根据模板参数T_
(创建对象时已知)告诉方法应该采用哪些参数类型?
一般来说,不会。 C++ 的规则明确允许进行隐式转换。 C++ 的作者使其中一些转换可能不安全的事实是另一回事。
如果用户输入错误的参数,您可以将 std::is_constructible<T,A&&...>
static_assert
或 SFINAE 添加到代码中,使编译器错误不那么难看,但它不会解决隐式转换。
从设计的角度来看,代码不应该关心这个,emplace_XXX
的目的是允许 T{args...}
.
允许的调用
注意:您很可能想转发 T element{std::forward<A>(args)...};
之类的参数,并将元素移动到向量 vec.push_back(std::move(t));
.
也就是说,代码
T_ t{args...};
//...
vec.push_back(t);
与 emplace
函数的作用完全相反,它们的目的是在其最终目的地就地创建元素。不要将其复制或移动到那里。
Is there any way to tell the method which parameters' type it is
supposed to take according to the template parameter T_ (which is
known when the object is created)?
在你的情况下,你应该使用直接初始化(()
)而不是列表初始化({}
)(以避免不必要的缩小检查)。
考虑 T
是 vector<int>
:
的情况
MyContainer<std::vector<int>> mc;
mc.add(3, 0);
您希望 mc.add(3, 0)
做什么?在您的 add()
函数中,T_ t{args...}
将调用 vector<int>{3,0}
并创建大小为 2 的 vector
,这显然是错误的。您应该使用 T_ t(args...)
调用 vector(size_type count, const T& value)
的重载来构建大小为 3 的向量,就像 emplace_back()
那样。
值得注意的是,由于P0960R3,如果T_
是聚合,T_ t(std::forward<Args>(args)...)
也可以进行聚合初始化。
你看错方向了。需要修复的不是模板,而是用户代码。要看问题,首先要明白2
是signed int
类型。所以你试图用 signed int
构造一个对象,而构造函数期望 long unsigned int
。然后可以如下编写该问题的最小重现。
class SomeClassRequiringAnUlong {
public:
SomeClassRequiringAnUlong(unsigned long) {}
};
int main() {
int v = 2;
SomeClassRequiringAnUlong obj{v};
}
警告只是指出,这种从 signed int
到 long unsigned int
的缩小转换具有潜在风险,可能会让您措手不及。例如,
int v = -1;
SomeClassRequiringAnUlong obj{v};
仍然编译运行,但结果可能不是你想要的。现在您看到问题出在用户代码上。我看到两种修复方法。 1) 从一开始就使用构造函数期望的类型。在您的情况下,这会将 mc.add(2)
更改为 mc.add(2ul)
。 2ul
是 long unsigned int
类型。 2) 显式进行类型转换,以通知编译器缩小转换是设计使然,并且没有问题。那就是将 mc.add(2)
更改为 mc.add(static_cast<long unsigned int>(2))
.
请注意,正如其他答案所指出的那样,您的模板中存在问题(尽管与警告不太相关)。但它们与您提出的具体问题无关。所以我不再详细介绍它们。
我在模板 class(T_
类型)中有一个可变参数模板方法,看起来像这样
template < typename T_ >
class MyContainer {
public:
...
template <typename ...A>
ulong add (A &&...args)
{
T_ t{args...};
// other stuff ...
vec.push_back(t);
// returning an ulong
}
}
所以基本上我试图让这个 class 适应任何类型 T_
但是因为我无法提前知道它的构造函数需要哪些类型,所以我使用了可变参数。我从 stl 库的 emplace_back
方法中获得灵感。
尽管如此,如果我尝试执行类似的操作,我会收到一条关于缩小整数类型转换的警告
MyContainer<SomeClassRequiringAnUlong> mc;
mc.add(2);
warning: narrowing conversion of ‘args#0’ from ‘int’ to ‘long unsigned int’ [-Wnarrowing]
所以我想知道我是否可以做些什么。有没有办法根据模板参数T_
(创建对象时已知)告诉方法应该采用哪些参数类型?
一般来说,不会。 C++ 的规则明确允许进行隐式转换。 C++ 的作者使其中一些转换可能不安全的事实是另一回事。
如果用户输入错误的参数,您可以将 std::is_constructible<T,A&&...>
static_assert
或 SFINAE 添加到代码中,使编译器错误不那么难看,但它不会解决隐式转换。
从设计的角度来看,代码不应该关心这个,emplace_XXX
的目的是允许 T{args...}
.
注意:您很可能想转发 T element{std::forward<A>(args)...};
之类的参数,并将元素移动到向量 vec.push_back(std::move(t));
.
也就是说,代码
T_ t{args...};
//...
vec.push_back(t);
与 emplace
函数的作用完全相反,它们的目的是在其最终目的地就地创建元素。不要将其复制或移动到那里。
Is there any way to tell the method which parameters' type it is supposed to take according to the template parameter T_ (which is known when the object is created)?
在你的情况下,你应该使用直接初始化(()
)而不是列表初始化({}
)(以避免不必要的缩小检查)。
考虑 T
是 vector<int>
:
MyContainer<std::vector<int>> mc;
mc.add(3, 0);
您希望 mc.add(3, 0)
做什么?在您的 add()
函数中,T_ t{args...}
将调用 vector<int>{3,0}
并创建大小为 2 的 vector
,这显然是错误的。您应该使用 T_ t(args...)
调用 vector(size_type count, const T& value)
的重载来构建大小为 3 的向量,就像 emplace_back()
那样。
值得注意的是,由于P0960R3,如果T_
是聚合,T_ t(std::forward<Args>(args)...)
也可以进行聚合初始化。
你看错方向了。需要修复的不是模板,而是用户代码。要看问题,首先要明白2
是signed int
类型。所以你试图用 signed int
构造一个对象,而构造函数期望 long unsigned int
。然后可以如下编写该问题的最小重现。
class SomeClassRequiringAnUlong {
public:
SomeClassRequiringAnUlong(unsigned long) {}
};
int main() {
int v = 2;
SomeClassRequiringAnUlong obj{v};
}
警告只是指出,这种从 signed int
到 long unsigned int
的缩小转换具有潜在风险,可能会让您措手不及。例如,
int v = -1;
SomeClassRequiringAnUlong obj{v};
仍然编译运行,但结果可能不是你想要的。现在您看到问题出在用户代码上。我看到两种修复方法。 1) 从一开始就使用构造函数期望的类型。在您的情况下,这会将 mc.add(2)
更改为 mc.add(2ul)
。 2ul
是 long unsigned int
类型。 2) 显式进行类型转换,以通知编译器缩小转换是设计使然,并且没有问题。那就是将 mc.add(2)
更改为 mc.add(static_cast<long unsigned int>(2))
.
请注意,正如其他答案所指出的那样,您的模板中存在问题(尽管与警告不太相关)。但它们与您提出的具体问题无关。所以我不再详细介绍它们。