Re: Variadic function to propagate array indexing doesn't work
correctly
Am 15.04.2012 08:10, schrieb Daryle Walker:
On Saturday, April 14, 2012 2:31:15 PM UTC-4, Christopher Creutzig wrote:
On 4/13/12 8:46 PM, Daryle Walker wrote:
int const table[3][3] = { {2, 3, 5}, {4, 6, 10}, {16, 36,
100} };
Make that
int const table[3][3] =
{{ {{2, 3, 5}}, {{4, 6, 10}}, {{16, 36, 100}} }};
I'm sure Daniel can explain why. :-)
I don't know how Christopher came to this conclusion, but I understand
his remark as a compliment and thank him for that.
One of you two should. All I get is an error: "braces around scalar
initializer for type 'const int'." Did you even suspect it would
work? (I didn't.) These are straight arrays, not arrays wrapped in
a struct.
I agree with your analysis, I don't think that this can be
well-formed.
[Should you have wondered why my code does not compile, you should
note that I used a simpler form of your RETURNS macro without the
ending-semicolon trick]
I removed the "const" to see if that made a difference, and it did!
Now all three calls error out with "no matching function for call to
'index_multiply(int [3][3])'." (One or two ", int" follow the
function prototype for the other errors.) I wonder why? I would
consider this a bug too.
No, this is not a bug, it is just a side-effect of your
exception-specification. My following description makes some
assertions as absolute ones that are only absolute in the context of
this specific example, because of the special nature of your
identity-conserving expressions denoted by __VA_ARGS__.
Lets look at the code w/o exception specification, then the macros
specifies perfect forwarding:
#define RETURNS(...) -> decltype(__VA_ARGS__) { return (__VA_ARGS__); }
Note that decltype is now identity-aware, and your code always
forwards glvalue identities (xvalues or lvalues), never prvalues,
therefore decltype(__VA_ARGS__) has always reference type
(lvalue-reference or rvalue-reference depending on the value category
of the initial arguments and depending on the effects of the
expression summarized by "__VA_ARGS__". Because the function call
*always* forwards references without any conversion in your example,
the function could be declared noexcept(true). If you do change the
exception-specification that way, it works with non-const arrays to.
Now what went wrong?
Array-indexing of the form E1[E2] has the same semantics as
*((E1)+(E2)), which means it will always return an lvalue irrespective
of the original value-category of E1 or E2 (This is so because of the
semantics of the dereference or indirection operator). There exists a
new core issue about this possibly surprising outcome in C++11 richer
expression world, see
http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1213
Now lets look at the exception-specification in your original example:
noexcept(noexcept(decltype(__VA_ARGS__)(my_move(__VA_ARGS__))))
The effect of my_move(__VA_ARGS__) is that it always converts its
argument to an rvalue (more specifically: xvalue), while
decltype(__VA_ARGS__) is always a reference-type but maybe an
lvalue-reference, if there was an lvalue provided (as in your code).
I'm not really sure what the intention of this exception-specification
is. One guess is that the intention is to protect the code from silent
rvalue-to-lvalue upgrades due to the above mentioned
array-subscription rule, but if that interpretation is correct, it
doesn't do that. The effect of the exception-specification is (whether
intended or not), that it allows only const lvalues or const rvalues
to enter.
Lets discuss some simpler example using your RETURNS macro:
#define RETURNS(...) \
noexcept(noexcept(decltype(__VA_ARGS__)(my_move(__VA_ARGS__)))) \
-> decltype(__VA_ARGS__) { return (__VA_ARGS__); }
template<class T, class U>
inline constexpr
auto slice(T&& t, U&& u)
RETURNS( my_forward<T>(t)[my_forward<U>(u)] )
The following static assertions are both ill-formed:
int arr[] = {1,2,3};
static_assert(std::is_same<decltype(slice(arr, 1)), int&>::value, "");
static_assert(std::is_same<decltype(slice(my_move(arr), 1)),
int&>::value, "");
because the expression
decltype(__VA_ARGS__)(my_move(__VA_ARGS__))
expands to
decltype(my_forward<T>(t)[my_forward<U>(u)])(my_move(my_forward<T>(t)[my_forward<U>(u)]))
The effects are that this unevaluated expression attempts to bind an
rvalue reference to int to an lvalue reference to non-const int, which
is not feasible. Not even via a reinterpret_cast, but this may change
in the future, see
http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1268
Let me emphasize that the expected return types above are *correct*,
the code is ill-formed because the noexcept-specification rejects the
function call expression. The form of the compiler-error clearly shows
that the overload is not silently rejected, which is a compiler
defect, because the exception-specification is also part of sfinae.
If we declare an array of const int, the binding is feasible,
therefore the revised static assertions
constexpr int carr[] = {1,2,3};
static_assert(std::is_same<decltype(slice(carr, 1)), const int&>::value,
"");
static_assert(std::is_same<decltype(slice(my_move(carr), 1)), const
int&>::value, "");
are both well-formed as expected.
The effects are essentially the same in your code example.
HTH & Greetings from Bremen,
Daniel Kr?gler
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]