C ++ class template constructor - overload reference (U &) with array (U *) failed

I'm trying to build a constructor to take an array as an argument that overloads another that uses a scalar instead. The code is below.

#include <iostream> template <typename T> class SmallVec { // This is a 3 dimensional vector class template public: T data[3] = {0}; // internal data of class template <typename U> explicit SmallVec(const U& scalar) { // if a scalar, copy it to each element in data for(auto &item : data) { item = static_cast<T>(scalar); } } template <typename U> explicit SmallVec(const U* vec) { // if a vector, copy one by one for(auto &item : data) { item = static_cast<T>(*vec); vec++; } } }; int main() { float num = 1.2; float *arr = new float[3]; arr[2] = 3.4; SmallVec<float> vec1(num); // take num, which works fine SmallVec<float> vec2(arr); // !!!--- error happens this line ---!!! std::cout << vec1.data[2] << " " << vec2.data[2] << std::endl; return 0; } 

The compiler complains that

 error: invalid static_cast from type 'float* const' to type 'float' 

Obviously, vec2(arr) still calls the first constructor. However, if I remove the template <typename U> and replace U with T The program is working fine. What should I do to fix this?

Any suggestions are welcome!

+5
source share
3 answers

I'm trying to build a constructor to take an array as an argument

(...)

 explicit SmallVec(const U* vec) { // if a vector, copy one by one 

You are not taking an array. Do you take a pointer that may or may not point to an array , and even if it points to an array that says the array has at least three elements? This is a serious design flaw.

C ++ allows you to take raw arrays by reference or constant reference, although the syntax is terrible:

 explicit SmallVec(const U (&vec)[3]) { 

The constructor implementation is also different:

  for(int index = 0; index < 3; ++index) { data[index] = static_cast<T>(vec[index]); } 

If you look at main , the problem goes deeper. You use new[] to dynamically allocate the array. This is already a very bad idea. By the way, your example also skips delete[] . Why aren't you using a local array instead?

  float arr[3]; 

This will make your program compile and probably work correctly, but it will still be undefined in your code, because you only set the 3rd element of the array to a valid value; the other two elements remain uninitialized, and reading from an uninitialized float , even if you just copy it, formally leads to undefined behavior.

So do it better:

  float arr[3] = { 0.0, 0.0, 3.4 }; 

In addition to this, C ++ 11 suggests you use std::array , which usually makes things safer and improves the syntax. Here is a complete example:

 #include <iostream> #include <array> template <typename T> class SmallVec { // This is a 3 dimensional vector class template public: std::array<T, 3> data; // internal data of class template <typename U> explicit SmallVec(const U& scalar) { // if a scalar, copy it to each element in data for(auto &item : data) { item = static_cast<T>(scalar); } } template <typename U> explicit SmallVec(std::array<U, 3> const& vec) { // if a vector, copy one by one for(int index = 0; index < 3; ++index) { data[index] = static_cast<T>(vec[index]); } } }; int main() { float num = 1.2; std::array<float, 3> arr = { 0.0, 0.0, 3.4 }; SmallVec<float> vec1(num); SmallVec<float> vec2(arr); std::cout << vec1.data[2] << " " << vec2.data[2] << std::endl; return 0; } 
+2
source

Here's how to use SFINAE to get what you want:

 #include <vector> #include <map> #include <string> using namespace std; template<class T> struct Foo { template <class U, typename enable_if<is_pointer<U>::value, int>::type = 0> Foo(U u){} template <class U, typename enable_if<!is_pointer<U>::value, int>::type = 0> Foo(U u){} }; int main() { Foo<int> f('a'); // calls second constructor Foo<int> f2("a"); // calls first constructor } 

live: https://godbolt.org/g/ZPcb5T

+2
source

Despite the fact that both constructors use an explicit qualifier and try to avoid type conversions, you should notice that the first is as good a candidate as the second. If you replace U with float *, you will get:

Explicit SmallVec (const float * & scalar)

which is quite acceptable and will explain the compilation error. You can solve the problem by changing the second constructor to:

 template <typename U> explicit SmallVec(U* const vec) { // if a vector, copy one by one U* local = vec; for(auto &item : data) { item = static_cast<T>(*local); local++; } } 

However, I suggest an even more explicit way:

 class ScalarCopy {}; class VectorCopy {}; ... template <typename U> SmallVec(const U& vec, ScalarCopy); template <typename U> SmallVec(const U* const vec, VectorCopy); 

and make explicit calls:

 SmallVec<float> vec1(num, ScalarCopy()); SmallVec<float> vec2(arr, VectorCopy()); 
+1
source

Source: https://habr.com/ru/post/1247801/


All Articles