#c #class #constructor #overload-resolution
#c #класс #конструктор #перегрузка-разрешение #c
Вопрос:
Это мой (урезанный) класс и экземпляр одного объекта:
template <typename T, typename Allocator = std::allocator<T> >
class Carray {
typedef typename Allocator::size_type size_type;
// ...
explicit Carray(size_type n, const Tamp; value, const Allocatoramp; alloc = Allocator()) {
// ...
}
template<typename InputIterator>
Carray(InputIterator first, InputIterator last, const Allocatoramp; alloc = Allocator()) {
// ...
}
// ...
}
Carray<int> array(5, 10);
Я бы ожидал, что это вызовет Carray(size_type, const Tamp;, const Allocatoramp;)
конструктор, но это не так. По-видимому, это решает template<typename InputIterator> Carray(InputIterator, InputIterator, const Allocatoramp;)
.
Что я должен изменить, чтобы это работало так, как задумано? Я тоже нахожу это странным, потому что вызов std::vector<int> v(5, 10)
работает совершенно нормально. И если я посмотрю на определение конструкторов в моей реализации GCC, я нахожу это (я переименовал некоторые имена реализаций компилятора, например __n
):
template<typename T, typename A = std::allocator<T> >
class vector {
typedef size_t size_type;
typedef T value_type;
typedef A allocator_type;
// ...
explicit vector(size_type n, const value_typeamp; value = value_type(), const allocator_typeamp; a = allocator_type());
template<typename InputIterator>
vector(InputIterator first, InputIterator last, const allocator_typeamp; a = allocator_type());
// ...
};
что, похоже, одно и то же.
Ответ №1:
Явный конструктор ожидает значения size_t и int . Вы предоставили два целых числа.
Замена int
на InputIterator
улучшает соответствие шаблона.
Если вы присмотритесь повнимательнее к стандартным контейнерам, вы увидите, что они используют некоторое шаблонное метапрограммирование, чтобы определить, может ли InputIterator
быть реальным итератором или это целочисленный тип. Затем это перенаправляет на другую конструкцию.
Редактировать
Вот один из способов сделать это:
template<class _InputIterator>
vector(_InputIterator _First, _InputIterator _Last,
const allocator_typeamp; _Allocator = allocator_type() )
: _MyAllocator(_Allocator), _MyBuffer(nullptr), _MySize(0), _MyCapacity(0)
{ _Construct(_First, _Last, typename std::is_integral<_InputIterator>::type()); }
private:
template<class _IntegralT>
void _Construct(_IntegralT _Count, _IntegralT _Value, std::true_type /* is_integral */)
{ _ConstructByCount(static_cast<size_type>(_Count), _Value); }
template<class _IteratorT>
void _Construct(_IteratorT _First, _IteratorT _Last, std::false_type /* !is_integral */)
{ _Construct(_First, _Last, typename std::iterator_traits<_IteratorT>::iterator_category()); }
Вы также могли бы использовать boost::type_traits, если у компилятора нет std::type_traits.
Комментарии:
1. И как бы я это решил? И
vector
конструктор также ожидает asize_t
и anint
, но при передачеint, int
он все равно разрешится на «правильный».2. @nightcracker:
std::vector
ведет себя таким образом, потому что стандарт требует, чтобы он вел себя таким образом. Это дополнительное требование, налагаемое стандартом поверх базового поведения основного языка. Если вы хотите, чтобы ваш класс вел себя точно так же, вам придется предпринять дополнительные шаги (точно так же, как это делаетstd::vector
). Вы можете взглянуть на конкретную реализациюstd::vector
, чтобы увидеть, как это делается там.3. @AndreyT: OM-fin-G. Я слеп. Это одна строка кода ниже объявления, и мне удалось ее проигнорировать. У него ДАЖЕ БЫЛ КОММЕНТАРИЙ, ОБЪЯСНЯЮЩИЙ ЭТО. Существует ли какой-либо стандартный способ сделать это без использования встроенных функций компилятора?
4. Есть ли какой-либо способ только с C 03 STL и без boost? Дело не в том, что я не хочу использовать boost, но я хочу, чтобы этот заголовочный файл был переносимым и по возможности избегал boost.
5. @nightcracker — Тогда вам пришлось бы выполнять is_integral самостоятельно. Стандартный шаблон — это просто базовый шаблон, по умолчанию имеющий значение false, и куча специализаций, имеющих значение true. Или tr1::type_traits для многих компиляторов.
Ответ №2:
Попробуйте это. Это исключит конструктор итератора из рассмотрения, если будут переданы два целых числа:
template<typename InputIterator>
Carray(InputIterator first, InputIterator last,
const Allocatoramp; alloc = Allocator(),
typename boost::disable_if<boost::is_integral<InputIterator> >::type* dummy = 0) {
}
Ссылка: http://www.boost.org/doc/libs/1_46_1/libs/utility/enable_if.html
РЕДАКТИРОВАТЬ: отвечая на «Есть ли какой-либо способ только с C 03 STL и без boost?»
Я не знаю, есть ли std::type_traits в C 03 или нет — у меня всегда есть boost, поэтому я просто использую его. Но вы можете попробовать это. В данном конкретном случае это сработает, но может не иметь требуемой общности:
template <class T> class NotInt { typedef void* type; };
template <> class NotInt<int> { };
template <typename T, typename Allocator = std::allocator<T> >
class Carray {
...
template<typename InputIterator>
Carray(InputIterator first, InputIterator last,
const Allocatoramp; alloc = Allocator(),
typename NotInt<InputIterator>::type t = 0) {
std::cout << __PRETTY_FUNCTION__ << "n";
}
};
Комментарии:
1. Есть ли какой-либо способ только с C 03 STL и без boost? Дело не в том, что я не хочу использовать boost, но я хочу, чтобы этот заголовочный файл был переносимым и по возможности избегал boost.
2. @nightcracker: Вы можете просто написать свой собственный. Такие признаки типа, как
is_integral
, относительно тривиальны для указания.
Ответ №3:
Это должно работать со всеми типами итераторов (включая указатели) и текущим стандартом.
#include <iostream>
#include <iterator>
#include <vector>
// uses sfinae to determine if the passed in type is indeed an iterator
template <typename T>
struct is_iterator_impl
{
typedef char yes[1];
typedef char no[2];
template <typename C>
static yesamp; _test(typename C::iterator_category*);
template <typename>
static noamp; _test(...);
static const bool value = sizeof(_test<T>(0)) == sizeof(yes);
};
template <typename T, bool check = is_iterator_impl<T>::value>
struct is_iterator
{
typedef void type;
};
template <typename T>
struct is_iterator<T, false>
{
};
template <typename T>
struct is_iterator<T*, false>
{
typedef void type;
};
template <typename T>
struct foo
{
explicit foo(std::size_t n, const Tamp; value)
{
std::cout << "foo:size_t" << std::endl;
}
template<typename InputIterator>
foo(InputIterator first, InputIterator last, typename is_iterator<InputIterator>::type* dummy = 0)
{
std::cout << "foo::iterator" << std::endl;
}
};
int main(void)
{
// should not cause a problem
foo<int> f(1, 2);
// using iterators is okay
typedef std::vector<int> vec;
vec v;
foo<int> b(v.begin(), v.end());
// using raw pointers - is okay
char bar[] = {'a', 'b', 'c'};
foo<char> c(bar, bar sizeof(bar));
}
Объяснение, итератор обычно должен определять несколько типов, таких как iterator_category
, и вы можете воспользоваться этим и sfinae для обнаружения реальных итераторов. Сложность в том, что указатели также являются итераторами, но у них не определены эти типы (что-то std::iterator_traits
обеспечивает специализацию для), поэтому в приведенном выше примере используется аналогичный подход, если переданный тип является указателем, то он по умолчанию обрабатывается как итератор. Такой подход избавляет вас от необходимости тестировать целочисленные типы.
Смотрите демонстрацию:http://www.ideone.com/E9l1T
Комментарии:
1. Спасибо, это позволяет мне полностью определить мой заголовок, не полагаясь ни на boost, ни на не-C 03. Конечно, я не буду использовать это в производственном коде (где boost::enable_if намного проще в использовании и уместен).
Ответ №4:
Первый конструктор ожидает, что аргумент ‘value’ будет передан по ссылке, в то время как второй конструктор ожидает, что первые 2 значения будут переданы по значению. По моему опыту, C довольно строго относится к этому различию, попробуйте передать целочисленную переменную вместо целочисленного значения в качестве 2-го аргумента вашему объектному конструктору.