Допустим, у меня есть функция шаблона, assign()
, Он принимает указатель и значение и назначает значение цели указателя:
template <typename T> void assign(T *a, T b) { *a = b; }
int main() {
double i;
assign(&i, 2);
}
В этом случае я всегда хочу T
быть выведенным из первого аргумента, но, похоже, я не очень хорошо выразил это. 2
Тип int
, так:
deduce.cpp: 5: 5: ошибка: нет подходящей функции для вызова 'assign' назначить (я, 2); ^ ~~~~~ deduce.cpp: 1: 28: примечание: шаблон кандидата игнорируется: обнаружены конфликтующие типы для параметра 'T' ('double' и 'int') шаблон void assign (T * a, T b) {* a = b; }
Есть ли способ, которым я могу объявить assign()
чтобы второй аргумент не участвовал в выводе параметров шаблона?
Использование двух параметров типа, вероятно, является наилучшим вариантом, но если вы действительно хотите выполнить вычитание только из первого аргумента, просто сделайте второй невыносимым:
template<typename T>
void assign( T* a, typename std::identity<T>::type b );
В более ранней версии этого ответа предлагалось использовать функцию псевдонима шаблона, представленную в C ++ 11. Но псевдонимы шаблонов все еще являются выводимым контекстом. Основная причина того, что std::identity
а также std::remove_reference
предотвращение вывода заключается в том, что классы шаблонов могут быть специализированными, поэтому даже если у вас есть typedef параметра типа шаблона, возможно, что другая специализация имеет typedef того же типа. Из-за возможной двусмысленности вычет не происходит. Но псевдонимы шаблонов исключают специализацию, поэтому дедукция все же происходит.
Проблема в том, что компилятор выводит противоречивую информацию из первого и второго аргумента. Из первого аргумента это выводит T
быть double
(i
это double)
; из второго это выводит T
быть int
(тип 2
является int
).
У вас есть две основные возможности здесь:
Всегда четко указывайте тип ваших аргументов:
assign(&i, 2.0);
// ^^^^
Или пусть ваш шаблон функции принимает два параметры шаблона:
template <typename T, typename U>
void assign(T *a, U b) { *a = b; }
В этом случае вы можете захотеть SFINAE-ограничить шаблон, чтобы он не участвовал в разрешении перегрузки в случае U
не конвертируется в T
:
#include <type_traits>
template <typename T, typename U,
typename std::enable_if<
std::is_convertible<U, T>::value>::type* = nullptr>
void assign(T *a, U b) { *a = b; }
Если вам не нужно исключать вашу функцию из набора перегрузки, когда U
не конвертируется в T
, вы можете захотеть иметь статическое утверждение внутри assign()
для получения более приятной ошибки компиляции:
#include <type_traits>
template<typename T, typename U>
void assign(T *a, U b)
{
static_assert(std::is_convertible<T, U>::value,
"Error: Source type not convertible to destination type.");
*a = b;
}
Просто ценность 2
выводится на тип int
, который не соответствует параметру шаблона, выведенному &i
, Вам нужно использовать значение как double:
assign(&i, 2.0);
Почему бы просто не использовать два независимые типы параметров, один для источника и один для пункта назначения?
template <typename D, typename S> void assign(D *a, S b) { *a = b; }
int main(int argc, char* argv[])
{
double i;
assign(&i, 2);
return 0;
}
Если назначение невозможно, создание шаблона не будет компилироваться.
Моя попытка будет выглядеть примерно так:
template<typename T, typename U>
typename std::enable_if< std::is_convertible< U&&, T >::value >::type // not quite perfect
assign( T* dest, U&& src ) {
*dest = std::forward<U>(src);
}
вторым аргументом является все, что вы можете преобразовать в T
, но мы берем его по универсальной ссылке и условно переносим в *dest
, Я проверяю на конвертируемость в сигнатуре, а не на том, чтобы тело не компилировалось, потому что ошибка «найти перегрузку» кажется более вежливой, чем неспособность скомпилировать тело.
По сравнению с более простым:
template<typename T>
void assign( T* dest, typename std::identity<T>::type src ) {
*dest = std::move(src);
}
выше сохраняет 1 move
, Если у вас дорогой класс для перемещения или класс, предназначенный только для копирования и дорогостоящий для копирования, это может сэкономить значительную сумму.
Кроме того, вы можете использовать decltype
чтобы ввести второй аргумент для типа первого.
template <typename T> void assign(T *a, T b) { *a = b; }
int main() {
double i;
assign(&i, (decltype(i))2);
}
По-видимому std::identity
больше нет (Есть ли причина, по которой в стандартной библиотеке нет std :: identity?)
Но вы можете указать тип параметра в списке типов параметров при вызове функции:
template <typename T> void assign(T *a, T b) { *a = b; }
int main() {
double i;
assign<double>(&i, 2);
}
Таким образом, компилятор преобразует входной аргумент целых чисел в double, чтобы соответствовать шаблону функции, без вывода аргумента.