事实上,我正在尝试实现类似于
std::initializer_list
的东西,但具有自定义初始化规则。
比如说,我们有
struct A
和 struct B
:
struct A
{
char a[16];
int b;
float c;
void print()
{
cout << a << b << c << endl;
}
};
struct B
{
int b;
float c;
void print()
{
cout << b << c << endl;
}
};
我们现在需要使用模板函数进行初始化:
template <typename T, typename ... Args>
void foo1(Args&& ... args)
{
T dummy {std::forward<Args>(args)...};
dummy.print();
}
这里遇到编译错误:
int main(int, char**) {
A a{"test", 3, 4}; // works fine
a.print(); // outputs: test34
// foo1<A>("test", 1, 2); // compile error: invalid conversion from 'const char*' to 'char' [-fpermissive]
foo1<B>(1, 2); // works fine and outputs: 12
return 0;
}
我尝试不使用
forward
(我希望它应该可以工作),同样的错误。
为
char *
定义一个 struct A
构造函数可能会解决问题,但我不认为这是一个通用的解决方案,毕竟该函数应该适用于随机类型。
我想知道,为什么我可以使用初始化列表直接初始化 char 数组,但在转发之后却不能?
有什么解决办法吗?
也许解压可变参数会有帮助?
这是不可能的。有一条特殊规则,允许从字符串文字初始化字符数组,并且仅直接从字符串文字初始化,而不是从指针或引用(in)到字符串文字或其他一般以空结尾的字符串或数组。
无法通过函数调用来转发此类初始化。
将内置字符数组替换为
std::array<char, 16>
,然后调用者就可以写入
foo1<A>(std::array<char, 16>{"test"}, 1, 2)
或者为其提供一个用户定义的字符串文字运算符,以便可以调用它,例如作为
foo1<A>("test"_Astring, 1, 2)
当然,将数组成员替换为
std::string
会更加灵活,然后就简单了
foo1<A>("test", 1, 2)
效果很好。
如果您不喜欢这些选项,那么您不能使用聚合初始化,并且需要为
A
编写适当的构造函数,而不是从指针/引用(in)复制到成员数组的字符串文字参数(例如,与 std::copy
或循环)。
顺便说一句,这里与
std::initializer_list
没有任何关系。它做了一些完全不同的事情:它明确地提供具有类似聚合的初始化语法的非聚合类的同质可变长度初始化。
另外,问题评论中提到的一个次要问题:
通常,在使用大括号进行初始化时,不允许所谓的“缩小转换”。缩小转换基本上是任何存在目标类型无法准确表示源值风险的转换,尽管实际规则更复杂。 特别是从整数类型到浮点类型的转换是一种缩小转换,因为浮点类型(通常)无法准确表示整数类型的所有值。
因此
foo1<B>(1, 2);
也是格式错误的,因为
2
是整数类型 int
,但 B
的目标成员是 float
类型。一些编译器无论如何都允许这样做,并且只发出缩小警告,但根据标准,它仍然是格式不正确的。如果转换的源值是常量表达式并且(然后编译时已知)值可以在目标类型中精确表示,则一般基于类型的转换是否缩小的确定有一个例外。
因为
2
完全可以表示为
float
,所以直接 B{1, 2}
不会是病态的。但这不适用于 f
,因为在 f
中,大括号中使用的值来自函数参数,该参数不能用作常量表达式,因为它的值取决于调用站点。