Use one argument to subtract a template parameter?

Suppose I have a template function, assign() . It takes a pointer and a value and assigns a value to the 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 be inferred from the first argument, but it looks like I didn't express it very well. 2 s is an int type, therefore:

  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 it possible to declare assign() so that the second argument does not participate in the output of the template parameter?

+6
source share
7 answers

Using two type parameters is probably the best option, but if you really want to output only from the first argument, just make the second irreducible:

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

An earlier version of this answer suggested using the template alias function introduced in C ++ 11. But template aliases are still an output context. The main reason std::identity and std::remove_reference prevents the deduction is because template classes can be specialized, so even if you have a typedef of a template type parameter, it is possible that another specialization has a typedef of the same type. Due to possible ambiguity, deduction does not occur. But template aliases preclude specialization, and therefore deduction still occurs.

+11
source

The problem is that the compiler outputs conflicting information from the first and second arguments. From the first argument, it infers T as double ( i is a double) ; from the second, it infers T as int (type 2 is int ).

Here you have two main options:

  • Always specify the type of arguments:

     assign(&i, 2.0); // ^^^^ 
  • Or let your template template accept two template parameters:

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

    In this case, you might want the SFINAE-restriction of the template so that it does not execute partecipate to overload the resolution if U not converted 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 not converted to T , you may want to have a static statement inside assign() to create a more pleasant 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; } 
+4
source

It's just that the value 2 is inferred to an int type that does not match the pattern parameter output by &i . You need to use the value as double:

 assign(&i, 2.0); 
+3
source

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 assignment is not possible, the template instance will not compile.

+2
source

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 all that you can convert to T , but we take it using a universal link and conditionally translate it to *dest . I test for convertibility in the signature, and not that the body does not compile, because refusing to search overload seems more polite than refusing to compile the body.

Living example .

Compared to 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 class transfer or a class that is only copied and expensive to copy, this can save a considerable amount.

+1
source

Alternatively, you can use decltype so that the type of the second argument is first.

 template <typename T> void assign(T *a, T b) { *a = b; } int main() { double i; assign(&i, (decltype(i))2); } 
0
source

Apparently std::identity is no longer there (is there a reason why std::identity is not in the standard library? )

But you can specify the type of parameter in the list of parameter types when calling the function:

 template <typename T> void assign(T *a, T b) { *a = b; } int main() { double i; assign<double>(&i, 2); } 

Thus, the compiler converts the integer input argument to double match the function template without outputting the argument.

Live demo

0
source

Source: https://habr.com/ru/post/948575/


All Articles