Reputation: 3
In fact, I'm trying to realize something similar to std::initializer_list
, but with custom initializing rules.
Say, we have struct A
and 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;
}
};
We now need to initialize with a template function:
template <typename T, typename ... Args>
void foo1(Args&& ... args)
{
T dummy {std::forward<Args>(args)...};
dummy.print();
}
Here I encounter a compile error:
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;
}
I tried not using the forward
(which I expected should work), same error.
Defining a char *
constructor for struct A
might just solve, but I don't think it is a general solution, after all the function should work for a random type.
I wonder, why can I directly initialize the char array using the initializer list, but not after a forward?
Any solution to this?
Maybe unpacking the variadic parameters could help?
Upvotes: 0
Views: 115
Reputation: 76829
This is not possible. There is a special rule that permits to initialize character arrays from string literals and only directly from string literals, not from pointers or references (in)to string literals or other null-terminated strings or arrays in general.
There is no way to forward this kind of initialization through a function call.
Replace your built-in character array with std::array<char, 16>
and then the caller can write
foo1<A>(std::array<char, 16>{"test"}, 1, 2)
Alternatively provide a user-defined string literal operator for it, so that it can be called e.g. as
foo1<A>("test"_Astring, 1, 2)
Of course, replacing the array member with std::string
would make it even more flexible and then simply
foo1<A>("test", 1, 2)
would work fine.
If you don't like these options, then you can't use aggregate initialization and need to write a proper constructor for A
instead that copies from a pointer/reference (in)to the string literal argument to the member array (e.g. with std::copy
or in a loop).
As a side note, nothing here has anything to do with std::initializer_list
. It does something completely different: It is meant explicitly to provide homogeneous variable-length initialization of non-aggregate classes with an aggregate-like initialization syntax.
Also, a secondary issue mentioned in the question comments:
Generally, when using braces for initialization so-called narrowing conversions are not permitted. A narrowing conversion is basically any conversion in which there is a risk that the target type can't exactly represent the source values, although the actual rules are more complicated.
In particular conversion from an integer type to a floating point type is a narrowing conversion, because a floating point type (usually) can't represent all values of an integer type exactly.
Therefore foo1<B>(1, 2);
is also ill-formed as 2
is a of integer type int
, but the target member of B
is of type float
. Some compilers permit this regardless and only issue a warning for narrowing, but it is still ill-formed according to the standard.
There is an exception to the general type-based determination of whether a conversion is narrowing, if the source value of the conversion is a constant expression and the (then compile-time known) value is exactly representable in the target type.
Because 2
is exactly representable as float
, then B{1, 2}
directly would not be ill-formed. This doesn't apply through f
though, because in f
the value used in the braces comes from the function parameter, which is not usable as a constant expression, since its value depends on the call site.
(Technically, with some limitations to the general case, you can write a function that would automatically fill the array member from a pointer by using a few tricks to determine for each individual member of the aggregate class if it is a character array and if so, to manually copy from the pointer to the array after aggregate initialization instead of passing on the argument to the initialization. But that requires some rather complex template machinery to implement this pseudo-reflection on the aggregate class.)
Upvotes: 1