CRTP: проблема, зависящая от компилятора, с шаблоном выражения

Я столкнулся с проблемой, зависящей от компилятора, со следующим кодом (хранящимся в crtp.cc):

#include <vector>
#include <cassert>
#include <iostream>

template < class Derived >
class AlgebraicVectorExpression {
public:
  typedef std::vector<double>::size_type  SizeType;
  typedef std::vector<double>::value_type ValueType;
  typedef std::vector<double>::reference  ReferenceType;

  SizeType size() const {
    return static_cast<const Derived&>(*this).size();
  }

  ValueType operator[](SizeType ii) const {
    return static_cast<const Derived&>(*this)[ii];
  }

  operator Derived&() {
    return static_cast<Derived&>(*this);
  }

  operator const Derived&() const {
    return static_cast< const Derived& >(*this);
  }
};

template< class T1, class T2>
class AlgebraicVectorSum : public AlgebraicVectorExpression< AlgebraicVectorSum<T1,T2> > {
  const T1 & a_;
  const T2 & b_;

  typedef typename AlgebraicVectorExpression< AlgebraicVectorSum<T1,T2> >::SizeType  SizeType;
  typedef typename AlgebraicVectorExpression< AlgebraicVectorSum<T1,T2> >::ValueType ValueType;
public:

  AlgebraicVectorSum(const AlgebraicVectorExpression<T1>& a, const AlgebraicVectorExpression<T1>& b) :
    a_(a), b_(b)  {
    assert(a_.size() == b_.size());
  }

  SizeType size() const {
    return a_.size();
  }

  ValueType operator[](SizeType ii) const {
    return (a_[ii] + b_[ii]);
  }

};

template< class T1, class T2>
const AlgebraicVectorSum<T1,T2>
operator+(const AlgebraicVectorExpression<T1>& a, const AlgebraicVectorExpression<T2>& b) {
  return AlgebraicVectorSum<T1,T2>(a,b);
}

class AlgebraicVector : public AlgebraicVectorExpression<AlgebraicVector>{
  std::vector<double> data_;

public:
  SizeType size() const {
    return data_.size();
  }

  ValueType operator[](SizeType ii) const {
    return data_[ii];
  }

  ValueType& operator[](SizeType ii) {
    return data_[ii];
  }

  AlgebraicVector(SizeType n) : data_(n,0.0) {
  };

  template< class T>
  AlgebraicVector(const AlgebraicVectorExpression<T>& vec) {
    const T& v = vec;
    data_.resize(v.size());
    for( SizeType idx = 0; idx != v.size(); ++idx) {
      data_[idx] = v[idx];
    }
  }
};

int main() {

  AlgebraicVector x(10);
  AlgebraicVector y(10);
  for (int ii = 0; ii != 10; ++ii)
    x[ii] = y[ii] = ii;    

  AlgebraicVector z(10);
  z = x + y;

  for(int ii = 0; ii != 10; ++ii)
    std::cout << z[ii] << std::endl;
  return 0;
}

На самом деле, когда я компилирую его с помощью:

$ g++ --version
g++ (Ubuntu 4.4.3-4ubuntu5) 4.4.3
Copyright (C) 2009 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ g++ -O0 -g crtp.cc

я получаю:

$ ./a.out 
0
2
4
6
8
10
12
14
16
18

что является ожидаемым поведением. Когда я использую icpc:

$ icpc --version
icpc (ICC) 12.1.0 20110811
Copyright (C) 1985-2011 Intel Corporation.  All rights reserved.    
$ icpc -g -O0 crtp.cc

Вместо этого я получаю Segmentation fault. Бег

valgrind --tool=memcheck ./a.out

указывает на строку 29 в источниках

AlgebraicVectorExpression<AlgebraicVector>::operator AlgebraicVector const&() const (crtp.cc:29)

Поскольку я новичок в C++ и потратил довольно много времени на поиск ошибки без каких-либо результатов, я хотел бы спросить мнение кого-то более опытного, чтобы понять, связана ли эта проблема с какой-то ошибкой, которую я внес (как я ожидаю) или к ошибке компилятора.

Изменить: я изменил код, как сейчас, после ответа Майка Сеймура. Теперь я не получаю предупреждений компилятора, но все равно получаю то же поведение, что и раньше (с тем же ответом valgrind). Кто-нибудь пробовал компилировать с помощью Intel?

Изменить: я попытался скомпилировать код в шаблонах выражений. страницу Википедии. Я получил точно такое же поведение, как и в приведенном мной примере.

Редактировать: я исследовал проблему более подробно, и кажется, что компиляция с помощью Intel icpc оператора

operator const Derived&() const {
    return static_cast< const Derived& >(*this);
  }

рекурсивно вызывает сам себя. Один обходной путь, который я нашел, состоит в том, чтобы заменить этот оператор методом:

const Derived& get_ref() const {
    return static_cast< const Derived& >(*this);
  }

и соответственно изменить конструкторы различных классов. Может ли кто-нибудь сказать, какое из этих двух поведений является правильным, возможно, указывая на стандарт, чтобы объяснить это?


person Massimiliano    schedule 16.03.2012    source источник


Ответы (1)


Вы всегда должны включать предупреждения компилятора; они часто могут обнаружить тонкие проблемы. В таком случае:

g++ -Wall -Wextra test.cpp
test.cpp: In member function ‘const typename AlgebraicVectorExpression<AlgebraicVectorSum<T1, T2> >::ValueType& AlgebraicVectorSum<T1, T2>::operator[](typename AlgebraicVectorExpression<AlgebraicVectorSum<T1, T2> >::SizeType) const [with T1 = AlgebraicVector, T2 = AlgebraicVector]’:
test.cpp:90:   instantiated from ‘AlgebraicVector::AlgebraicVector(const AlgebraicVectorExpression<T1>&) [with T = AlgebraicVectorSum<AlgebraicVector, AlgebraicVector>]’
test.cpp:103:   instantiated from here
test.cpp:52: warning: returning reference to temporary

Это говорит вам о проблеме:

const ValueType& operator[](SizeType ii) const {
    return (a_[ii] + b_[ii]);
}

Результат выражения является временным, уничтожается в конце этой строки, поэтому функция возвращает висячую ссылку на несуществующий объект. Вместо этого этот оператор должен будет возвращать значение, и вам не следует реализовывать перегрузку non-const, так как нет значения для изменения.

person Mike Seymour    schedule 16.03.2012
comment
Неконстантный operator[] также страдает от той же проблемы, его нельзя создавать, поскольку я не получил предупреждения об этом. - person hmjd; 16.03.2012
comment
@hmjd: в любом случае вы не можете разумно реализовать неконстантный вариант для AlgebraicVectorSum. Константная версия должна просто возвращать значение вместо ссылки, и все будет в порядке. Мне также любопытно, что вы получите, имея AlgebraicVectorExpression и вообще используя CRTP в этом случае. - person Omnifarious; 16.03.2012
comment
Возможно, мне следует больше подумать об этом, но все ссылки должны указывать в конце на значения x или y в main, следовательно, на значения, которые все еще существуют после того, как временное исчезнет. Во всяком случае, я попытался изменить код, возвращаемый значением, и получил тот же результат, что и раньше (хотя во время компиляции не было предупреждения). - person Massimiliano; 16.03.2012
comment
@Massimiliano: Нет, в случае AlgebraicVectorSum значение рассчитывается по запросу; нет постоянного значения, на которое можно было бы вернуть ссылку. В коде выше это совершенно ясно - operator[] возвращает ссылку на результат a[i] + b[i], который является временным. - person Mike Seymour; 16.03.2012
comment
@Mike Seymour: большое спасибо за четкое объяснение (теперь я понял вашу точку зрения :-)). Во всяком случае, кажется, что есть еще одна проблема с operator const Derived&() const при компиляции с icpc, которая вызывает ошибку сегментации. - person Massimiliano; 17.03.2012