Summary
With XTENSOR_USE_XSIMD defined, assigning from an expression that contains
xt::reshape_view(<lazy expression>, shape) fails to compile with
xtensor/views/xstrided_view.hpp:688:60: error: lvalue required as unary ‘&’ operand
688 | return xt_simd::load_as<requested_type>(&(storage()[i]), align_mode());
| ~~~~~~~~~~^~~~
The same code compiles and produces correct results without XTENSOR_USE_XSIMD,
and it also compiles with xsimd if the argument of reshape_view is an
evaluated container instead of a lazy expression. So the trigger is specifically
reshape_view over an unevaluated xfunction while the SIMD assign path is enabled.
This is a hard error inside a template body, not an SFINAE-friendly rejection, so
it cannot be worked around by the caller other than by pre-evaluating the operand
or by not enabling xsimd globally.
Reproducer (fails)
#include <cstddef>
#include <xtensor/containers/xtensor.hpp>
#include <xtensor/views/xstrided_view.hpp>
int main() {
const std::size_t G = 8, N = 4;
xt::xtensor<double, 1> w({G}, 1.5), v({G}, 2.0);
xt::xtensor<double, 2> Phi({G, N}, 3.0);
auto col = xt::reshape_view(w * v, {G, std::size_t(1)}); // lazy xfunction
xt::xtensor<double, 2> out = Phi * col; // broadcast assign
return out(0, 0) == 9.0 ? 0 : 1;
}
g++ -std=c++20 -O2 -march=native -DXTENSOR_USE_XSIMD repro.cpp # COMPILE ERROR
g++ -std=c++20 -O2 -march=native repro.cpp # OK, correct
Control (compiles, with and without xsimd)
Pre-evaluating the operand makes the strided view container-backed and everything works:
xt::xtensor<double, 1> wv = w * v; // evaluated
auto col = xt::reshape_view(wv, {G, std::size_t(1)});
xt::xtensor<double, 2> out = Phi * col; // OK with -DXTENSOR_USE_XSIMD
Measured matrix:
operand of reshape_view |
XTENSOR_USE_XSIMD off |
on |
lazy w * v |
compiles, correct |
compile error |
evaluated xtensor<double,1> |
compiles, correct |
compiles, correct |
Root cause (as far as I can trace it)
xstrided_view gates its SIMD interface on the wrapped expression:
// xtensor/views/xstrided_view.hpp:183
static constexpr bool provides_simd_interface = has_simd_interface<xexpression_type>::value
&& L != layout_type::dynamic;
For reshape_view(w * v, …) the xexpression_type is xfunction<multiplies, …>,
which does provide a SIMD interface, so provides_simd_interface is true and
xassign selects linear_assigner<simd_assign=true>.
But load_simd / store_simd do not delegate to xexpression_type::load_simd —
they take the address of the view's own flat storage:
// xtensor/views/xstrided_view.hpp:688 (load_simd), :678 (store_simd)
return xt_simd::load_as<requested_type>(&(storage()[i]), align_mode());
For a lazy operand the FST is detail::flat_adaptor_getter<xclosure_t<E>, L>
(xstrided_view.hpp:870), whose storage type is
detail::flat_expression_adaptor<xfunction<…>> (xstrided_view_base.hpp:251-263).
Its reference / const_reference are inherited from the wrapped expression
(xstrided_view_base.hpp:40-44), and for an xfunction those are prvalues
(computed on access), not lvalue references. Taking & of that is ill-formed.
So provides_simd_interface tests the wrong property: it asks "can the wrapped
expression vectorize?", while the two functions it guards actually require "does
this view's storage expose a contiguous lvalue buffer?".
Suggested direction
Add the storage requirement to the constraint, e.g. also require that
decltype(std::declval<const storage_type&>()[0]) is an lvalue reference (or,
equivalently, that FST is not a flat_adaptor_getter over a non-container
expression), so that lazy-expression-backed strided views simply fall back to the
non-SIMD assign path instead of hard-erroring. Alternatively, load_simd/store_simd
could forward to the underlying expression's own load_simd/store_simd when the
flat storage is a computed adaptor.
Environment
- xtensor 0.27.1 (also
xtl 0.8.2, xsimd 14.2.0), fetched via CMake FetchContent
- GCC 14.3.0,
-std=c++20 -O2 -march=native (AVX-512 target: avx512vnni/avx512vbmi2)
- Linux x86_64
I searched open/closed issues for load_simd, xstrided_view simd,
reshape_view XTENSOR_USE_XSIMD and lvalue required and did not find this one;
#2025 (where + xsimd) is a runtime-value bug on a different path, so I opened a
separate report. Happy to test a patch — the reproducer above is the whole case.
Summary
With
XTENSOR_USE_XSIMDdefined, assigning from an expression that containsxt::reshape_view(<lazy expression>, shape)fails to compile withThe same code compiles and produces correct results without
XTENSOR_USE_XSIMD,and it also compiles with xsimd if the argument of
reshape_viewis anevaluated container instead of a lazy expression. So the trigger is specifically
reshape_viewover an unevaluatedxfunctionwhile the SIMD assign path is enabled.This is a hard error inside a template body, not an SFINAE-friendly rejection, so
it cannot be worked around by the caller other than by pre-evaluating the operand
or by not enabling xsimd globally.
Reproducer (fails)
Control (compiles, with and without xsimd)
Pre-evaluating the operand makes the strided view container-backed and everything works:
Measured matrix:
reshape_viewXTENSOR_USE_XSIMDoffw * vxtensor<double,1>Root cause (as far as I can trace it)
xstrided_viewgates its SIMD interface on the wrapped expression:For
reshape_view(w * v, …)thexexpression_typeisxfunction<multiplies, …>,which does provide a SIMD interface, so
provides_simd_interfaceistrueandxassignselectslinear_assigner<simd_assign=true>.But
load_simd/store_simddo not delegate toxexpression_type::load_simd—they take the address of the view's own flat storage:
For a lazy operand the
FSTisdetail::flat_adaptor_getter<xclosure_t<E>, L>(
xstrided_view.hpp:870), whose storage type isdetail::flat_expression_adaptor<xfunction<…>>(xstrided_view_base.hpp:251-263).Its
reference/const_referenceare inherited from the wrapped expression(
xstrided_view_base.hpp:40-44), and for anxfunctionthose are prvalues(computed on access), not lvalue references. Taking
&of that is ill-formed.So
provides_simd_interfacetests the wrong property: it asks "can the wrappedexpression vectorize?", while the two functions it guards actually require "does
this view's storage expose a contiguous lvalue buffer?".
Suggested direction
Add the storage requirement to the constraint, e.g. also require that
decltype(std::declval<const storage_type&>()[0])is an lvalue reference (or,equivalently, that
FSTis not aflat_adaptor_getterover a non-containerexpression), so that lazy-expression-backed strided views simply fall back to the
non-SIMD assign path instead of hard-erroring. Alternatively,
load_simd/store_simdcould forward to the underlying expression's own
load_simd/store_simdwhen theflat storage is a computed adaptor.
Environment
xtl0.8.2,xsimd14.2.0), fetched via CMakeFetchContent-std=c++20 -O2 -march=native(AVX-512 target:avx512vnni/avx512vbmi2)I searched open/closed issues for
load_simd,xstrided_view simd,reshape_view XTENSOR_USE_XSIMDandlvalue requiredand did not find this one;#2025 (
where+ xsimd) is a runtime-value bug on a different path, so I opened aseparate report. Happy to test a patch — the reproducer above is the whole case.