Reputation: 5965
I want to have a wide template that 'does whatever it needs to' except for when I have explicitly specified the case.
Specifically, I am overloading operator()
to use it for a matrix index of a multidimensional matrix. I also want to allow specifying an arbitrary number of indices using an iterator. Ideally I'd have the following signatures:
operator()(size_t);
operator()(size_t,size_t);
operator()(size_t,size_t,size_t);
...
template<class Iterator> operator()(Iterator,Iterator);
The problem is that operator()(size_t,size_t)
is never reached, because the compiler is also able to template template<class Iterator> operator()(Iterator,Iterator)
. How can I avoid this?
An obvious solution is to use std::vector<size_t>::iterator
instead of Iterator
. I've tried this, but this narrows the usage elsewhere.
A minimal example:
#include <iostream>
class Foo
{
private:
double data[9];
public:
Foo(){};
double& operator()(size_t i, size_t j)
{
std::cout << "operator()(size_t i, size_t j)" << std::endl;
return data[0];
}
template<class Iterator>
double& operator()(Iterator first, Iterator last)
{
std::cout << "operator()(Iterator first, Iterator last)" << std::endl;
return data[0];
}
};
int main()
{
Foo bar;
bar(0,1);
}
Outputs:
operator()(Iterator first, Iterator last)
whereas I want this case to output
operator()(size_t i, size_t j)
Upvotes: 2
Views: 73
Reputation: 118340
It's almost a certainty that the reason your template gets selected by overload resolution is because the two parameters you're passing in are not really size_t
. They're probably int
s, or something else. If they were truly size_t
s, then I would expect your non-template overload to be picked. Cleaning that up should make things work, as is, but it's simple enough to make this work in any case.
The usual approach in this kind of a situation is to use SFINAE to exclude the template from participating in overload resolution when the passed-in parameter is size_t
. Something along the lines of (using C++17):
template<class Iterator,
typename=std::enable_if_t<
std::negation_v<std::is_integral_v<Iterator>>>> operator()(Iterator,Iterator)
{
// ...
}
This is your starting point. It's tempting to use std::is_same_v<Iterator,size_t>
, but you'll quickly discover that this only works if you're passing in exactly a size_t
. It's very easy for an int
to slip in there, if you're not careful, and this is going to fall apart in this case. So you'll probably need to use std::is_integral_v
. Hopefully you're not passing in floating point values anywhere, and rely on them being truncated to the next nearest integer value. If you do, you'll have to tweak this further.
The std::is_integral_v
and std::enable_if_t
shortcuts are available only in C++17 (as well as std::void_t
), but it's simple enough to reinvent that wheel in earlier standards, if necessary.
You can also try using SFINAE in the opposite direction: have this template participate in overload resolution only if Iterator
resolved to something that std::iterator_traits
recognizes. The best approach depends on your specific class's requirements.
Upvotes: 3