Please help me understand what is causing the error I describe below.
I wish to abstract a SIMD vector type (4 x single-precision floats) and a naive equivalent (that just uses standard C++ operators - i.e. iterate through vector explicitly) into a single interface, so that they can be used interchangeably and selected at compile-time. Within a namespace I have defined a base class so that my actual implementation classes can use the CRTP pattern (and therefore selectively override functions when necessary, or use the base class's implementation, without invoking a virtual function call). I'm aiming for static (compile-time) polymorphism here, and inlining where possible, as I need this to be fast.
For now I have only defined the index operator, however eventually I will define operators for addition, subtraction, etc, via the CRTP base class, allowing each derived class to override them as appropriate for their storage mechanism.
vector.h:
#ifndef VECTOR_H
#define VECTOR_H
namespace vector {
// CRTP-style base class
template <typename VectorType>
class BaseVector {
public:
// provide casts to the derived class pointer
VectorType & asVector() { return static_cast<VectorType &>(*this); }
const VectorType & asVector() const { return static_cast<const VectorType &>(*this); }
float & operator[](size_t i) { return asVector()[i]; }
// not returning 'const &' because references to SIMD vector elements are not allowed:
float operator[](size_t i) const { return asVector()[i]; }
};
// The Barton-Nackman trick involves using the CRTP pattern to isolate the following template
// to only those types that are related to BaseVector:
template <typename StreamType, typename VectorType>
StreamType & operator<<(StreamType & s, const BaseVector<VectorType> & v) {
return s << "[ " << v[0] << ", " << v[1] << ", " << v[2] << ", " << v[3] << " ]";
}
class Vector4f : public BaseVector<Vector4f> {
public:
template <typename T>
Vector4f(T x0, T x1, T x2, T x3) :
storage_({static_cast<float>(x0),
static_cast<float>(x1),
static_cast<float>(x2),
static_cast<float>(x3)}) {}
float & operator[](size_t i) { return storage_[i]; }
const float & operator[](size_t i) const { return storage_[i]; }
protected:
typedef std::vector<float> StorageType;
StorageType storage_;
};
class Vector4f_SIMD : public BaseVector<Vector4f_SIMD> {
public:
template <typename T>
Vector4f_SIMD(T x0, T x1, T x2, T x3) :
storage_((StorageType){static_cast<float>(x0),
static_cast<float>(x1),
static_cast<float>(x2),
static_cast<float>(x3)}) {}
float operator[](size_t i) { return storage_[i]; }
// cannot use references to SIMD vector element
float operator[](size_t i) const { return storage_[i]; }
protected:
typedef float StorageType __attribute__((__vector_size__(16)));
StorageType storage_;
};
} // namespace vector
#endif // VECTOR_H
I'm compiling with C++11 (clang). Note the use of the Barton-Nackman trick to isolate the template match to only these classes. I'm not 100% sure why this is necessary with C++11, but if I don't use it, the template seems to override the standard operator<< for char * as the second type, creating an ambiguity error, which I don't want to do.
The stream serialisation template seems to work for ostream:
std::cout << vector::Vector4f(1,2,3,4) << std::endl; // compiles
std::cout << vector::Vector4f_SIMD(1,2,3,4) << std::endl; // compiles
Where I run into problems is with GoogleTest's AssertionFailure class, that can be used with a stream operator to add information. I'm looking to use a helper function to check that a vector contains the values I expect (without relying on an equality operator that doesn't yet exist):
test_vector.cc:
#include <gtest/gtest.h>
#include "vector.h"
using namespace vector;
// type-parameterized tests
template <typename T>
class TestVector : public ::testing::Test {};
typedef ::testing::Types<vector::Vector4f, vector::Vector4f_SIMD> TypesToTest;
TYPED_TEST_CASE(TestVector, TypesToTest);
template <typename T, typename U>
static ::testing::AssertionResult CheckVector(const T & v, const U & x0, const U & x1, const U & x2, const U & x3) {
return v[0] == x0 && v[1] == x1 && v[2] == x2 && v[3] == x3 ? ::testing::AssertionSuccess() : ::testing::AssertionFailure() << v << " does not equal [ " << x0 << ", " << x1 << ", " << x2 << ", " << x3 << " ]";
}
TYPED_TEST(TestVector, ctor_by_float_parameters) {
TypeParam v(0.0f, 0.1f, 0.2f, 0.3f);
// Note this line causes a compiler error when uncommented:
//EXPECT_TRUE(CheckVector(v, 0.0, 0.1, 0.2, 0.3));
}
For completeness, I'm compiling this with googletest built in the directory below my source:
$ cd googletest
$ g++ -isystem include -Iinclude -I. -pthread -c src/gtest-all.cc
$ ar -rv libgtest.a gtest-all.o
$ g++ -isystem include -Iinclude -I. -pthread -c src/gtest-main.cc
$ ar -rv libgtest_main.a gtest_main.o
$ cd ..
$ g++ -std=c++11 test_vector.cc -isystem googletest/include googletest/libgtest.a googletest/libgtest_main.a -o test_vector
When the EXPECT_TRUE(CheckVector... line above is uncommented, the compiler fails with this first error:
In file included from test_vector.cpp:2:
./vector.hpp:23:10: error: non-const lvalue reference to type 'std::__1::basic_stringstream<char,
std::__1::char_traits<char>, std::__1::allocator<char> >' cannot bind to a value of unrelated type
'basic_ostream<char, std::__1::char_traits<char> >'
return s << "[ " << v[0] << ", " << v[1] << ", " << v[2] << ", " << v[3] << " ]";
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
googletest/include/gtest/gtest-message.h:131:10: note: in instantiation of function template specialization
'vector::operator<<<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >,
vector::Vector4f_SIMD>' requested here
*ss_ << val;
^
googletest/include/gtest/gtest.h:306:29: note: in instantiation of function template specialization
'testing::Message::operator<<<vector::Vector4f_SIMD>' requested here
AppendMessage(Message() << value);
^
test_vector.cpp:15:127: note: in instantiation of function template specialization
'testing::AssertionResult::operator<<<vector::Vector4f_SIMD>' requested here
...&& v[2] == x2 && v[3] == x3 ? ::testing::AssertionSuccess() : ::testing::AssertionFailure() << v << "[ " << x0 <...
^
test_vector.cpp:20:15: note: in instantiation of function template specialization 'CheckVector' requested here EXPECT_TRUE(CheckVector(v, 0.0, 0.1, 0.2, 0.3));
From what I understand, it is having trouble applying the result of the operator<< template for my Vector base class to the operator<< for the testing::AssertionFailure class. I don't understand why this is causing a problem as it ought to be invoking the AssertionFailure's operator after it has serialised my vector into a stringstream. There's something going on here that I don't yet understand.
Is this problem caused by my use of CRTP, or even the Barton-Nackman trick? Is there a better way to do this and still avoid dynamic polymorphism?
I could avoid using this AssertionFailure class entirely, however I would like to understand what is happening, and how to fix it, rather than just giving up and trying something different.
Any help appreciated, please.
Aucun commentaire:
Enregistrer un commentaire