views:

150

answers:

4

Hello!

How should I handle the following situation :

I am writing my own 2D vector class and have the following code:

class Vector2 : public (...)
public:

   Vector2(float x, float y) {

      local_vector_storage_[0] = x;
      local_vector_storage_[1] = y;
   }

   template <typename Iterator> Vector2(Iterator begin, Iterator end) {

      ASSERT(end - begin == 2);

      resize(2);

      std::copy(begin, end, local_vector_storage_.begin());
   }

// ...
};

Now if I say Vector2 v(3.0f, 4.0f); it compiles fine and calls the appropriate float constructor.

But if I write Vector2 v(3, 4); it fails because the templated iterator constructor "fits better" and Vector2(Iterator(3), Iterator(4)) is called.

What should I do in this case?

My idea was about introducing assign(It1, It2) member method instead of the constructor but maybe there is a better solution?

Edit:

Also, what do you think about ASSERT(end - begin == 2) line? I know that this means I can't, for example, pass iterators of std::list, but brings additional safety. Should I do this or not?

+6  A: 

Something like this seems to work:

template<typename T>
struct notnumeric {typedef int OK;};

template<>
struct notnumeric<int> {};

class Vector2
{
public:
   Vector2(float x, float y)
   {
   }

   template <typename Iterator>
   Vector2(Iterator begin, Iterator end, typename notnumeric<Iterator>::OK dummy = 0)
   {
   }
};

I believe it's using SFINAE to prevent the compiler selecting the second ctor for non-numeric types.

As for ASSERT (end - begin == 2), I think you should be using std::distance(begin, end) to determine the distance between two iterators.

jon hanson
You can improve it by using `std::enable_if<!std::is_numeric<Iterator>::value, Iterator>::type`. This will remove the "dummy" parameter and uses compiler-provided (or Boost, if you're desperate) type traits, which are much more reliable than custom-written type traits. In addition, you could throw, instead of using assert.
DeadMG
Agreed that is better (if it works), though i think `enable_if` is only in boost until C++0x.
jon hanson
@DeadMG: How can enable_if remove the dummy on a constructor? I thought that only worked on methods with return types.
Staffan
@Staffan: Enable_if works wherever a type declaration may be used. In this case, the arguments. People just use the return types most commonly. @Jon Hanson: It's in TR1 too.
DeadMG
+1  A: 

EDIT: Is this a 2d vector? Or just two vectors? I answered this for a 2d vector.

How should I handle the following situation

I think you should handle it by removing the float constructor. From reading the code, it's unclear what kind of object you should expect from this.

From reading the callsite code, I would have no reason to believe that vector2 v2(1, 5); creates a vector of two vectors, each with one value.

Personally, I would have expected it to create a 1x5 matrix.

If this is a common use case for your library, consider a named constructor:

vector2 Create2x1(float f1, float f2);

re: ASSERT

The ASSERT is a nice sanity check, but requires your Iterator to support random access (or at least subtraction to find distance). This might overly restrict its usage. Consider using a std::distance or checking that local_vector_storage is size two afterwards.

Stephen
It seems pretty clear to me that `vector v2(1, 5)` is constructing a 2-dimensional vector with one dimension having magnitude 1 and the other 5. The `local_vector_storage` member holds the scalar values of each dimension not other vectors. OP could change that name, but the vector API makes sense (at least to me!)
Neil Williams
@Neil : Hmmm... now I'm thoroughly confused. Maybe you're right, but why make the dimensions floats? Maybe so it compiles? Assigning `x` and `y` to the local_storage seems weird since the actual storage is never resized.
Stephen
+1  A: 

In the particular case I see no sense of introducing the Vector2(Iterator begin, Iterator end) c'tor at all.

In general, I see no sense to mimic the std::vector (which is essentially a wrapper for an array) when the dimension of your Vector2 is fixed and never change. Overlap of use-cases between the std::vector and your Vector2 is negligible to non-existent: std::vector is often initialized from another container, while the Vector2 would be 50/50 initialized with two values or with another Vector2.

And even if your decide to go ahead, the line:

ASSERT(end - begin == 2);

would drastically limit usefulness of the constructor, as relatively few iterators support the arithmetic.

Dummy00001
+1  A: 

For the call resolution here is a detailed why from Herb Sutter

DumbCoder