Использовать один аргумент для вывода параметров шаблона?

Допустим, у меня есть функция шаблона, 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() чтобы второй аргумент не участвовал в выводе параметров шаблона?

7

Решение

Использование двух параметров типа, вероятно, является наилучшим вариантом, но если вы действительно хотите выполнить вычитание только из первого аргумента, просто сделайте второй невыносимым:

template<typename T>
void assign( T* a, typename std::identity<T>::type b );

В более ранней версии этого ответа предлагалось использовать функцию псевдонима шаблона, представленную в C ++ 11. Но псевдонимы шаблонов все еще являются выводимым контекстом. Основная причина того, что std::identity а также std::remove_reference предотвращение вывода заключается в том, что классы шаблонов могут быть специализированными, поэтому даже если у вас есть typedef параметра типа шаблона, возможно, что другая специализация имеет typedef того же типа. Из-за возможной двусмысленности вычет не происходит. Но псевдонимы шаблонов исключают специализацию, поэтому дедукция все же происходит.

13

Другие решения

Проблема в том, что компилятор выводит противоречивую информацию из первого и второго аргумента. Из первого аргумента это выводит 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;
    }
    
4

Просто ценность 2 выводится на тип int, который не соответствует параметру шаблона, выведенному &i, Вам нужно использовать значение как double:

assign(&i, 2.0);
3

Почему бы просто не использовать два независимые типы параметров, один для источника и один для пункта назначения?

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;
}

Если назначение невозможно, создание шаблона не будет компилироваться.

2

Моя попытка будет выглядеть примерно так:

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, Если у вас дорогой класс для перемещения или класс, предназначенный только для копирования и дорогостоящий для копирования, это может сэкономить значительную сумму.

1

Кроме того, вы можете использовать decltype чтобы ввести второй аргумент для типа первого.

template <typename T> void assign(T *a, T b) { *a = b; }

int main() {
double i;
assign(&i, (decltype(i))2);
}
0

По-видимому std::identity больше нет (Есть ли причина, по которой в стандартной библиотеке нет std :: identity?)

Но вы можете указать тип параметра в списке типов параметров при вызове функции:

template <typename T> void assign(T *a, T b) { *a = b; }

int main() {
double i;
assign<double>(&i, 2);
}

Таким образом, компилятор преобразует входной аргумент целых чисел в double, чтобы соответствовать шаблону функции, без вывода аргумента.

Живая демо

0
По вопросам рекламы [email protected]