В приведенном ниже определении функции:
inline auto get_a() const { return arg.get_a() * s; };
ваше ожидаемое поведение таково, что auto
выводит std::valarray<double>
, то есть тип результата умножения std::valarray<double>
и int
, которыйновый объект, который уже хранит значения, умноженные на целое число.
Вот как определяется operator*
[valarray.binary] / p2 :
template <class T>
valarray<T> operator*(const valarray<T>&,
const typename valarray<T>::value_type&);
Однакоесть следующий абзац в стандарте [valarray.syn] / p3 :
Любая функция, возвращающая valarray<T>
, может вернуть объект другого типа, при условии, что все функции-константы valarray<T>
также применимы к этому типу.Этот возвращаемый тип не должен добавлять более двух уровней вложенности шаблонов к наиболее глубоко вложенному типу аргумента.
Этот тип должен быть преобразован в std::valarray<double>
, но сам по себе, в целях оптимизации, может не представлятьфактический результат до того, как произойдет это преобразование.
То есть вот фактический тип, выведенный для GCC auto
:
std::_Expr<std::__detail::_BinClos<std::__multiplies
, std::_ValArray
, std::_Constant, double, double>, double>
и вот что использует Clang:
std::__1::__val_expr<std::__1::_BinaryOp<std::__1::multiplies<double>,
std::__1::valarray<double>, std::__1::__scalar_expr<double> > >
Другими словами, вы возвращаете по значению объект, который, вероятно, откладывает фактические вычисления.Чтобы сделать это, эти промежуточные объекты должны как-то хранить отложенные подвыражения.
Осматривая реализацию GCC libstdc ++, можно найти следующее представление:
template <class _Oper, class _FirstArg, class _SecondArg>
class _BinBase
{
public:
typedef typename _FirstArg::value_type _Vt;
typedef typename __fun<_Oper, _Vt>::result_type value_type;
_BinBase(const _FirstArg& __e1, const _SecondArg& __e2)
: _M_expr1(__e1), _M_expr2(__e2) {}
// [...]
private:
const _FirstArg& _M_expr1;
const _SecondArg& _M_expr2;
};
Обратите внимание, что подвыражения хранятся как ссылка .Это означает, что в определении get_a()
:
return arg1.get_a() + arg2.get_a();
_M_expr1
и _M_expr2
связаны с временными объектами:
arg1.get_a()
arg2.get_a()
т.е. промежуточные объекты, являющиеся результатом умножения, время жизни которых заканчивается, как только выходит NextedAdd::get_a()
, приводит к неопределенному поведению , когда результат в конечном итогевычисляется, в частности, когда реализация пытается получить доступ к каждому отдельному элементу этих промежуточных подвыражений:
value_type operator[](size_t __i) const
{
return _Oper()(_M_expr1[__i], _M_expr2[__i]);
}
Быстрое решение состоит в том, чтобы использовать следующий тип возврата:
std::decay_t<decltype(arg.get_a())> get_a() const { return arg.get_a() * s; }
Thisбудет рекурсивно гарантировать, что конечный тип результата любой операции будет таким, какой был исходный тип T
в Nested<T>
, т. е. std::valarray<double>
.
DEMO