5

I am trying to build a constructor to take an array as an argument which overloads another who take a scalar instead. 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 template <typename U> and replace U to T. The program just works fine. What should I do to correct this?

Any suggestions are appreciated!

4

3 回答 3

3

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

于 2016-04-24T07:48:46.700 回答
2

I am 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 do not take an array. You take a pointer, which may or may not point to an array, and even if it points to an array, who says that the array has at least three elements? That's a serious design flaw.

C++ does allow you to take raw arrays by reference or const reference, even though the syntax is horrible:

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

The implementation of the constructor is then also different:

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

Looking at main, however, the problem goes deeper. You use new[] to allocate an array dynamically. That's already a very bad idea. Coincidentally, your example also misses a delete[]. Why don't you use a local array instead?

 float arr[3];

This will make your program compile and probably run correctly, but there's still undefined behaviour in your code, because you only set the 3rd element of the array to a valid value; the other two elements remain uninitialised, and reading from an uninitialised float, even if you just copy it, formally results in undefined behaviour.

So better make it:

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

In addition to that, C++11 invites you to use std::array, which generally makes things a bit 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;
}
于 2016-04-24T10:52:28.743 回答
1

Even though both constructors use the explicit specifier and try to avoid type conversions you should note that the first is just as good a candidate as the second. If you substitute U for float* you will get:

explicit SmallVec(const float*& scalar)

which is totally acceptable and will explain the compilation error. You could resolve 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());
于 2016-04-24T08:17:27.380 回答