Я реализовал подмножество forward_list
и хотел проверить метод assign(size_type __n, const _Tp& __val)
но я получаю ошибку компилятора, потому что компилятор хочет вызвать метод assign(_InputIterator __first, _InputIterator __last)
вместо.
Я написал следующий фрагмент, чтобы проиллюстрировать проблему:
test.h
#ifndef TEST_H
#define TEST_H
#include <utility> // Just to get the std::size_t
template<typename _Tp>
class forward_list {
public:
typedef std::size_t size_type;
void assign(size_type n, const _Tp& val)
{
printf("%s\n", __PRETTY_FUNCTION__);
}
template<typename _InputIterator>
void assign(_InputIterator first, _InputIterator last)
{
printf("%s\n", __PRETTY_FUNCTION__);
}
};
#endif // TEST_H
test.cpp
#include <stdlib.h>
#include <stdio.h>
#include "test.h"
int main()
{
forward_list<int> l;
l.assign(10, 5);
return 0;
}
Результат выполнения:
void forward_list<_Tp>::assign(_InputIterator, _InputIterator) [with _InputIterator = int; _Tp = int]
Я хотел бы иметь метод assign(size_type __n, const _Tp& __val)
называется.
Версия компилятора (на всякий случай): g++ (Debian 4.7.2-5) 4.7.2
Я использовал аналогичные подписи к подписям, используемым в std::forward_list
и со следующим фрагментом кода (используя STL):
std::forward_list<int> l;
l.assign(10, 5);
Компилятор знает, что он должен вызывать assign(size_type __n, const _Tp& __val)
и не запутаться. Что мне не хватает?
Когда вы звоните l.assign(10, 5);
Есть две жизнеспособные перегрузки:
void assign(size_type n, const int& val)
template <>
void assign(int first, int last)
Когда мы говорим, что не шаблонные функции предпочтительнее шаблонных функций, это верно только в том случае, если они имеют неразличимые последовательности преобразования. Но в этом случае шаблон функции будет точно соответствовать (оба ваших аргумента int
, нет необходимости в конвертации), в то время как не шаблон должен проходить промоушен (продвигать 10
от int
в size_t
). Вот почему перегрузка шаблона функции предпочтительнее.
Что касается того, как это исправить, вам просто нужно сделать шаблон не жизнеспособная перегрузка. Это включает в себя написание type_trait для входного итератора, который использует void_t
не сложно
template <typename... >
using void_t = void;
template <typename T, typename = void>
struct is_input_iterator : std::false_type { };
template <typename T>
struct is_input_iterator<T, void_t<
decltype(std::declval<T>() == std::declval<T>()),
decltype(std::declval<T>() != std::declval<T>()),
decltype(*std::declval<T>()),
decltype(++std::declval<T>()),
decltype(std::declval<T>()++)
>> : std::true_type { };
А потом требуют is_input_iterator
:
template <typename _InputIterator,
typename = std::enable_if_t<is_input_iterator<_InputIterator>::value>>
void assign(_InputIterator first, _InputIterator last);
Есть много других способов сделать это, мне просто нравится void_t
, Независимо от того, каким образом вы это делаете, вы должны убедиться, что шаблон просто не жизнеспособен.
assign
перегрузка, которую вы хотите вызвать, принимает unsigned
целочисленный тип в качестве первого аргумента, но вы передаете ему два целых числа со знаком. Если вы измените вызов на
l.assign(10U, 5); // make the first argument unsigned
первый assign
перегрузка называется. Но ясно, что это не правильное решение вашей проблемы в целом.
Вы должны ограничить assign
шаблон, так что он жизнеспособен только тогда, когда тип аргументов удовлетворяет требованиям для итераторов. Один из способов сделать это — проверить iterator_traits
для вовлеченного типа. Если iterator_category
удовлетворяет требованиям InputIterator, тогда можно использовать шаблон функции.
template<typename InputIterator>
typename std::enable_if<
std::is_base_of<std::input_iterator_tag,
typename std::iterator_traits<InputIterator>::iterator_category
>::value
>::type
assign(InputIterator first, InputIterator last)
{
printf("%s\n", __PRETTY_FUNCTION__);
}
Обратите внимание, что технически вышеупомянутое решение не гарантированно работает до C ++ 17 (или как бы оно ни называлось), потому что iterator_traits
не требуется быть SFINAE дружественным до тех пор, и это может привести к серьезной ошибке вместо ошибки замены. Но шансы вашей реализации iterator_traits
уже СФИНАЕ дружественный, и вы не столкнетесь ни с какими проблемами.
size_t
не гарантировано быть включенным <utility>
, используйте один из заголовков, перечисленных на связанной странице.
Не используйте идентификаторы, которые начинаются с символа подчеркивания и сопровождаются заглавными буквами, это зарезервированный для реализации.