views:

623

answers:

7

I'm working on code that does nearest-neighbor queries. There are two simple ideas that underlie how a user might query for data in a search:

  • closest N points to a given point in space.
  • all points within a given distance.

In my code, Points are put into a PointList, and the PointList is a container has the job of keeping track of the points that have been found in the search.

Right now my PointList object has one constructor:

PointList( unsigned int maxvals ); // #1

The next two constructors that I'd like to add are these:

PointList( float maxdist ); // #2
PointList( unsigned int maxvals, float maxdist ); // #3

My question is: how do I ensure that my users and the C++ compiler will generate the right constructor for the PointList and differentiates between constructors 1 and 2? Should I just implement #3 and provide constants that define arbitrary large values for maxvals and maxdist? Another alternative might be to write another system of lightweight objects that govern the logic for adding Points to the list, but that feels like overkill for such a simple idea.

I'm really trying to make this transparent for my users, who are mostly scientists who have learned C++ sometimes without the benefit of formal education. Thanks!

+1  A: 

Call PointList(10) for the first and PointList(10f) for the second.

For the second, you can also use 10.0.

Burkhard
That's definitely a possibility, but my users might not understand the difference. Some of them might be unsophisticated about the way that the compiler differentiates between floats and unsigned ints.
James Thompson
+6  A: 

Why not use factory methods instead of constructors? Factory methods have the advantage of customizable names.


static PointList createNearestValues(unsigned int maxvals) {}
static PointList createByDistance(float maxdist) {}
Hans Malherbe
+1  A: 

If constructors #1 and #2 are present the correct constructor will be called if the value you insert is of float or int and no conversion should take place. So just ensure that you make the types of the numbers you use to call explicit (i.e. 1f and 1). Constructor #3 doesn't seem to be much of an option as it is not really necessary and would just confuse users of your code. If you need default values for either number you could use

PointList(int max, float max=VALUE)

and

PointList(float max, int max=VALUE)

Again: this seems to do more harm then code in terms of code readability.

pmr
+1  A: 

This is calling for a good reading on Overload Resolution.

Ariel
+4  A: 

Overload resolution for integer types happen on two categories, which can be very roughly summarized to

  • Promotion: This is a conversion from types smaller than int to int or unsigned int, depending on whether int can store all the values of the source type.
  • Conversion: This is a conversion from any integer type to another integer type.

Similar, conversion for floating point types happen on two categories

  • Promotion: This is a conversion from float to double
  • Conversion: This is a conversion from any floating point type to another floating point type

And there is a conversion from integer to floating or back. This is ranked as a conversion, rather than a promotion. A promotion is ranked better than a conversion, and where only a promotion is needed, that case will be preferred. Thus, you can use the following constructors

PointList( int maxVals );
PointList( unsigned int maxVals );
PointList( long maxVals );
PointList( unsigned long maxVals );

PointList( double maxDist );
PointList( long double maxDist );

For any integer type, this should select the first group of constructor. And for any floating point type, this should select the second group of constructors. Your original two constructors could easily result in an ambiguity between float and unsigned int, if you pass an int, for example. For the other, two argument constructor, you can go with your solution, if you want.


That said, i would use a factory function too, because deciding on the type the meaning of the parameter is quite fragile i think. Most people would expect the following result to equal

PointList p(floor(1.5));
PointList u((int)1.5);

But it would result in a different state of affairs.

Johannes Schaub - litb
+2  A: 

Consider using true typedefs. It's a little more effort on the part of your client code, but you're guaranteed correctness.

DannyT
A: 

I would definitely use explicit constructors. In the example the unsigned integer does not convert implicitly.

class A
{
public:
 explicit A(float f){}
 explicit A(int i){}
};

void test(){
 unsigned int uinteger(0);
 A a1(uinteger);        //Fails, does not allow implicit conversions

 A a2((float)uinteger); //OK, explicit conversion

 float f(0.0);
 A a3(f);               //OK

 int integer(0);
 A a4(integer);        //OK
}

The error message is easy enough to understand:

: error C2668: 'A::A' : ambiguous call to overloaded function
: could be 'A::A(int)'
: or       'A::A(float)'
mandrake