Reputation: 51685
Let’s say I have a template function, assign()
. It takes a pointer and a value and assigns the value to the pointer’s target:
template <typename T> void assign(T *a, T b) { *a = b; }
int main() {
double i;
assign(&i, 2);
}
In this case I always want T
to be deduced from the first argument, but it looks like I didn’t do a good job of expressing this. 2
’s type is int
, so:
deduce.cpp:5:5: error: no matching function for call to 'assign' assign(&i, 2); ^~~~~~ deduce.cpp:1:28: note: candidate template ignored: deduced conflicting types for parameter 'T' ('double' vs. 'int') template void assign(T *a, T b) { *a = b; }
Is there a way I can declare assign()
so that the second argument doesn’t participate in template parameter deduction?
Upvotes: 7
Views: 2573
Reputation: 342
C++20 has std::type_identity
which can be used to establish a non-deduced context:
#include <type_traits>
template <typename T>
void assign(T *a, std::type_identity_t<T> b) {
*a = b;
}
int main() {
double i;
assign(&i, 2);
}
Upvotes: 0
Reputation: 6521
Apparently std::identity
is not there anymore (Is there a reason why there is not std::identity in the standard library?)
But you can specify the parameter type in the parameter type list, when calling the function:
template <typename T> void assign(T *a, T b) { *a = b; }
int main() {
double i;
assign<double>(&i, 2);
}
In this way the compiler will convert the integer input argument to double to match the function template, without argument deduction.
Upvotes: 0
Reputation: 275385
My attempt would look something like this:
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);
}
the second argument is anything you can convert to a T
, but we take it by universal reference and conditionally move it into *dest
. I test for convertability in the signature rather than have the body fail to compile, because failure-to-find-an-overload seems more polite than failing to compile-the-body.
Compared to the simpler:
template<typename T>
void assign( T* dest, typename std::identity<T>::type src ) {
*dest = std::move(src);
}
the above saves 1 move
. If you have an expensive to move class, or a class that is copy-only and expensive to copy, this could save a significant amount.
Upvotes: 1
Reputation: 38146
Alternatively, you can use decltype
to typecast the second argument to be the type of first.
template <typename T> void assign(T *a, T b) { *a = b; }
int main() {
double i;
assign(&i, (decltype(i))2);
}
Upvotes: 0
Reputation: 35594
Why not just use two independent parameter types, one for the source and one for the destination?
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;
}
If the assignment is not possible, the template instantiation won't compile.
Upvotes: 2
Reputation: 283634
Using two type parameters is probably the best option, but if you really want to perform deduction only from the first argument, simply make the second non-deducible:
template<typename T>
void assign( T* a, typename std::identity<T>::type b );
An earlier version of this answer suggested using the template alias feature introduced in C++11. But template aliases are still a deducible context. The primary reason that std::identity
and std::remove_reference
prevents deduction is that template classes can be specialized, so even if you have a typedef of a template type parameter, it's possible that another specialization has a typedef of the same type. Because of the possible ambiguity, deduction doesn't take place. But template aliases preclude specialization, and so deduction still occurs.
Upvotes: 13
Reputation: 126432
The problem is that the compiler is deducing conflicting information from the first and the second argument. From the first argument, it deduces T
to be double
(i
is a double)
; from the second one, it deduces T
to be int
(the type of 2
is int
).
You have two main possibilities here:
Always be explicit about the type of your arguments:
assign(&i, 2.0);
// ^^^^
Or let your function template accept two template parameters:
template <typename T, typename U>
void assign(T *a, U b) { *a = b; }
In this case, you may want to SFINAE-constraint the template so that it does not partecipate to overload resolution in case U
is not convertible to 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; }
If you do not need to exclude your function from the overload set when U
is not convertible to T
, you may want to have a static assertion inside assign()
to produce a nicer compilation error:
#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;
}
Upvotes: 4
Reputation: 96810
It's just that the value 2
is deduced to the type int
, which doesn't match the template parameter deduced by &i
. You need to use the value as a double:
assign(&i, 2.0);
Upvotes: 3